import { z } from "zod";
import {
getUserContext,
getWorkspaceSelection,
listOrgWorkspaces,
listUserOrganisations,
setWorkspaceSelection,
} from "../helpers/getUser.js";
import { squadClient } from "../lib/clients/squad.js";
import { logger } from "../lib/logger.js";
import {
formatWorkspaceSelectionError,
getUserId,
type OAuthServer,
toolError,
toolSuccess,
WorkspaceSelectionRequired,
} from "./helpers.js";
/**
* Register workspace tools with the MCP server
*/
export function registerWorkspaceTools(server: OAuthServer) {
// List available workspaces
server.tool(
{
name: "list_workspaces",
title: "List Workspaces",
description:
"List all organisations and workspaces available to the current user. Use this to see what workspaces you can switch to.",
schema: z.object({}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
},
},
async (_params, ctx) => {
try {
const userId = getUserId(ctx.auth);
const token = ctx.auth.accessToken;
const orgs = await listUserOrganisations(token);
const result: Array<{
org: { id: string; name: string };
workspaces: Array<{ id: string; name: string }>;
}> = [];
for (const org of orgs) {
const workspaces = await listOrgWorkspaces(token, org.id);
result.push({ org, workspaces });
}
const currentSelection = getWorkspaceSelection(userId);
return toolSuccess({
currentSelection: currentSelection || null,
available: result,
});
} catch (error) {
logger.debug({ err: error, tool: "list_workspaces" }, "Tool error");
const message =
error instanceof Error ? error.message : "Unknown error";
return toolError(`Unable to list workspaces: ${message}`);
}
},
);
// Select workspace
server.tool(
{
name: "select_workspace",
title: "Select Workspace",
description:
"Select which organisation and workspace to use for subsequent operations. Required when user has access to multiple orgs/workspaces.",
schema: z.object({
orgId: z.string().describe("The ID of the organisation to select"),
workspaceId: z.string().describe("The ID of the workspace to select"),
}),
},
async (params, ctx) => {
try {
const userId = getUserId(ctx.auth);
const token = ctx.auth.accessToken;
const { orgId, workspaceId } = params;
// Verify the user has access to this org/workspace
const orgs = await listUserOrganisations(token);
const org = orgs.find(o => o.id === orgId);
if (!org) {
return toolError(
`Organisation ${orgId} not found or you don't have access to it.`,
);
}
const workspaces = await listOrgWorkspaces(token, orgId);
const workspace = workspaces.find(w => w.id === workspaceId);
if (!workspace) {
return toolError(
`Workspace ${workspaceId} not found in organisation ${org.name}.`,
);
}
// Store the selection
setWorkspaceSelection(userId, orgId, workspaceId);
return toolSuccess({
message: `Switched to workspace "${workspace.name}" in organisation "${org.name}"`,
org: { id: orgId, name: org.name },
workspace: { id: workspaceId, name: workspace.name },
});
} catch (error) {
logger.debug({ err: error, tool: "select_workspace" }, "Tool error");
const message =
error instanceof Error ? error.message : "Unknown error";
return toolError(`Unable to select workspace: ${message}`);
}
},
);
// Get Workspace
server.tool(
{
name: "get_workspace",
title: "Get Workspace Details",
description:
"Get details of the current workspace. Workspaces contain the project name, detailed description, and mission statement.",
schema: z.object({}),
annotations: {
readOnlyHint: true,
destructiveHint: false,
},
},
async (_params, ctx) => {
try {
const userId = getUserId(ctx.auth);
const userContext = await getUserContext(ctx.auth.accessToken, userId);
const { orgId, workspaceId } = userContext;
const workspace = await squadClient(userContext).getWorkspace({
orgId,
workspaceId,
});
return toolSuccess(workspace);
} catch (error) {
if (error instanceof WorkspaceSelectionRequired) {
return toolError(formatWorkspaceSelectionError(error));
}
logger.debug({ err: error, tool: "get_workspace" }, "Tool error");
const message =
error instanceof Error ? error.message : "Unknown error";
return toolError(`Unable to retrieve workspace: ${message}`);
}
},
);
// Update Workspace
server.tool(
{
name: "update_workspace",
title: "Update Workspace",
description:
"Update the current workspace's details such as name, description, mission statement.",
schema: z.object({
name: z.string().optional().describe("Updated name for the workspace"),
homepageUrl: z
.string()
.optional()
.describe("Updated URL to the workspace's homepage"),
logoUrl: z
.string()
.optional()
.describe("Updated URL to the workspace's logo"),
missionStatement: z
.string()
.optional()
.describe("Updated mission statement for the workspace"),
description: z
.string()
.optional()
.describe("Updated detailed description of the workspace"),
}),
annotations: {
readOnlyHint: false,
destructiveHint: true,
},
},
async (params, ctx) => {
try {
const userId = getUserId(ctx.auth);
const userContext = await getUserContext(ctx.auth.accessToken, userId);
const { orgId, workspaceId } = userContext;
const workspace = await squadClient(userContext).updateWorkspace({
orgId,
workspaceId,
updateWorkspacePayload: params,
});
return toolSuccess(workspace);
} catch (error) {
if (error instanceof WorkspaceSelectionRequired) {
return toolError(formatWorkspaceSelectionError(error));
}
logger.debug({ err: error, tool: "update_workspace" }, "Tool error");
const message =
error instanceof Error ? error.message : "Unknown error";
return toolError(`Unable to update workspace: ${message}`);
}
},
);
}