Files
Orchestrator/bahn/db-planet-mcp-server/src/tools/workspaceTools.ts
T
ankn a5f8fb49ab Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
2026-06-30 20:39:52 +02:00

157 lines
6.3 KiB
TypeScript

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<CallToolResult> => {
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<any>({
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<string, any> = {};
if (page !== undefined) params._page = page;
if (pageSize !== undefined) params._pageSize = pageSize;
const data = await client.request<any>({ 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<CallToolResult> => {
try {
const data = await client.request<any>({ 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<CallToolResult> => {
try {
const data = await client.request<any>({ 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<CallToolResult> => {
try {
const data = await client.request<any>({ 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<CallToolResult> => {
try {
const meData = await client.request<any>({ method: "GET", path: "/api/users/me" });
const data = await client.request<any>({
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);
}
}
);
}