import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { HaiiloClient } from "../haiiloClient.js"; import { handleApiError } from "../errorHandler.js"; export function registerWorkspaceTools(server: McpServer, client: HaiiloClient): void { const s = server as any; s.tool( "Workspace_List", "List workspaces on DB Planet. Use memberOnly=true to show only workspaces the current user is a member of (recommended for 'my workspaces' queries). Without the filter, returns one page of workspaces.", { memberOnly: z.boolean().optional().describe("If true, only return workspaces where the current user is a member or admin. Defaults to false."), page: z.number().optional().describe("Page number (0-based). Defaults to 0."), pageSize: z.number().optional().describe("Number of workspaces per page. Defaults to 20."), }, async ({ memberOnly, page, pageSize }: any): Promise => { try { if (memberOnly) { // Paginate through all workspaces to find the user's memberships const allMyWorkspaces: any[] = []; let currentPage = 0; const batchSize = 100; let hasMore = true; while (hasMore) { const data = await client.request({ method: "GET", path: "/api/workspaces", params: { _page: currentPage, _pageSize: batchSize }, }); const items = data?.content || (Array.isArray(data) ? data : []); for (const ws of items) { if (ws.membershipStatus && ws.membershipStatus !== "NONE") { allMyWorkspaces.push(ws); } } hasMore = items.length === batchSize; currentPage++; } if (allMyWorkspaces.length === 0) { return { content: [{ type: "text", text: "You are not a member of any workspaces." }] }; } let text = `You are a member of ${allMyWorkspaces.length} workspace(s):\n\n`; allMyWorkspaces.forEach((ws: any, i: number) => { text += `[${i + 1}] ${ws.name || ws.displayName || "Untitled"}\n`; text += ` ID: ${ws.id}\n`; text += ` Slug: ${ws.slug || "—"}\n`; text += ` Role: ${ws.membershipStatus}\n`; text += ` Visibility: ${ws.visibility || "—"}\n`; text += ` Members: ${ws.memberCount || 0}\n`; text += ` Description: ${(ws.description || "—").substring(0, 150)}\n\n`; }); return { content: [{ type: "text", text }] }; } // Default: return a single page const params: Record = {}; if (page !== undefined) params._page = page; if (pageSize !== undefined) params._pageSize = pageSize; const data = await client.request({ method: "GET", path: "/api/workspaces", params }); const text = typeof data === "string" ? data : JSON.stringify(data, null, 2); return { content: [{ type: "text", text }] }; } catch (error) { return handleApiError(error); } } ); s.tool( "Workspace_Get", "Get workspace details by ID or slug. The id parameter must be a WORKSPACE UUID or slug, NOT a user ID. Use Workspace_List or Search_QuickEntity to find workspace IDs first.", { id: z.string().describe("Workspace UUID or slug (NOT a user ID)") }, async ({ id }: any): Promise => { try { const data = await client.request({ method: "GET", path: `/api/workspaces/${id}` }); const text = typeof data === "string" ? data : JSON.stringify(data, null, 2); return { content: [{ type: "text", text }] }; } catch (error) { return handleApiError(error); } } ); s.tool( "Workspace_GetMembers", "Get members of a specific workspace. NOT for finding or listing workspaces — use Workspace_List or Search_QuickEntity first to find workspace IDs. The id parameter must be a WORKSPACE UUID, NOT a user ID.", { id: z.string().describe("Workspace UUID (NOT a user ID — use Workspace_List or Search_QuickEntity to find it)") }, async ({ id }: any): Promise => { try { const data = await client.request({ method: "GET", path: `/api/workspaces/${id}/members` }); const text = typeof data === "string" ? data : JSON.stringify(data, null, 2); return { content: [{ type: "text", text }] }; } catch (error) { return handleApiError(error); } } ); s.tool( "Workspace_Join", "Join a workspace as the current user. Use Workspace_List or Search_QuickEntity to find the ID first.", { id: z.string().describe("Workspace UUID") }, async ({ id }: any): Promise => { try { const data = await client.request({ method: "PUT", path: `/api/workspaces/${id}/users/join` }); return { content: [{ type: "text", text: `Successfully joined workspace ${id}.` }] }; } catch (error) { return handleApiError(error); } } ); s.tool( "Workspace_Create", "Create a new workspace on DB Planet.", { name: z.string().describe("Workspace name"), visibility: z.string().optional().describe("Visibility: PUBLIC, CLOSED, or PRIVATE. Defaults to PUBLIC."), }, async ({ name, visibility }: any): Promise => { try { const meData = await client.request({ method: "GET", path: "/api/users/me" }); const data = await client.request({ method: "POST", path: "/api/workspaces", data: { id: null, slug: null, name, description: "", categoryIds: [], adminIds: [meData.id], adminGroupIds: [], memberIds: [], memberGroupIds: [], visibility: visibility || "PUBLIC", defaultLanguage: null, translations: {}, archived: false, }, }); const text = typeof data === "string" ? data : JSON.stringify(data, null, 2); return { content: [{ type: "text", text }] }; } catch (error) { return handleApiError(error); } } ); }