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.
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user