UNPKG

@the_cfdude/productboard-mcp

Version:

Model Context Protocol server for Productboard REST API with dynamic tool loading

327 lines (326 loc) 11.7 kB
/** * Users management tools */ import { withContext, formatResponse } from '../utils/tool-wrapper.js'; import { normalizeListParams, normalizeGetParams, filterByDetailLevel, filterArrayByDetailLevel, isEnterpriseError, } from '../utils/parameter-utils.js'; import { fetchAllPages } from '../utils/pagination-handler.js'; import { ProductboardError } from '../errors/index.js'; import { ErrorCode } from '@modelcontextprotocol/sdk/types.js'; export function setupUsersTools() { return [ { name: 'create_user', description: 'Create a new user in Productboard', inputSchema: { type: 'object', properties: { email: { type: 'string', description: 'User email address', }, name: { type: 'string', description: 'User full name', }, role: { type: 'string', description: 'User role (e.g., admin, member, viewer)', }, companyId: { type: 'string', description: 'Company ID to associate with the user', }, externalId: { type: 'string', description: 'External ID for the user', }, instance: { type: 'string', description: 'Productboard instance name (optional)', }, workspaceId: { type: 'string', description: 'Workspace ID (optional)', }, }, required: ['email'], }, }, { name: 'get_users', description: 'List all users in Productboard', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Maximum number of users to return (1-100, default: 100)', }, startWith: { type: 'number', description: 'Offset for pagination (default: 0)', }, detail: { type: 'string', enum: ['basic', 'standard', 'full'], description: 'Level of detail (default: basic)', }, includeSubData: { type: 'boolean', description: 'Include nested complex JSON sub-data', }, instance: { type: 'string', description: 'Productboard instance name (optional)', }, workspaceId: { type: 'string', description: 'Workspace ID (optional)', }, }, }, }, { name: 'get_user', description: 'Get a specific user by ID', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'User ID', }, detail: { type: 'string', enum: ['basic', 'standard', 'full'], description: 'Level of detail (default: standard)', }, includeSubData: { type: 'boolean', description: 'Include nested complex JSON sub-data', }, instance: { type: 'string', description: 'Productboard instance name (optional)', }, workspaceId: { type: 'string', description: 'Workspace ID (optional)', }, }, required: ['id'], }, }, { name: 'update_user', description: 'Update an existing user', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'User ID', }, name: { type: 'string', description: 'Updated user name', }, role: { type: 'string', description: 'Updated user role', }, companyId: { type: 'string', description: 'Updated company ID', }, externalId: { type: 'string', description: 'Updated external ID', }, instance: { type: 'string', description: 'Productboard instance name (optional)', }, workspaceId: { type: 'string', description: 'Workspace ID (optional)', }, }, required: ['id'], }, }, { name: 'delete_user', description: 'Delete a user', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'User ID', }, instance: { type: 'string', description: 'Productboard instance name (optional)', }, workspaceId: { type: 'string', description: 'Workspace ID (optional)', }, }, required: ['id'], }, }, ]; } export async function handleUsersTool(name, args) { try { switch (name) { case 'create_user': return await createUser(args); case 'get_users': return await listUsers(args); case 'get_user': return await getUser(args); case 'update_user': return await updateUser(args); case 'delete_user': return await deleteUser(args); default: throw new Error(`Unknown users tool: ${name}`); } } catch (error) { const enterpriseInfo = isEnterpriseError(error); if (enterpriseInfo.isEnterpriseFeature) { throw new ProductboardError(ErrorCode.InvalidRequest, enterpriseInfo.message, error); } throw error; } } async function createUser(args) { return await withContext(async (context) => { const body = { email: args.email, }; if (args.name) body.name = args.name; if (args.role) body.role = args.role; if (args.companyId) body.company = { id: args.companyId }; if (args.externalId) body.externalId = args.externalId; const response = await context.axios.post('/users', body); return { content: [ { type: 'text', text: formatResponse({ success: true, user: response.data, }), }, ], }; }, args.instance, args.workspaceId); } async function listUsers(args) { return await withContext(async (context) => { const normalized = normalizeListParams(args); const params = {}; // Use proper pagination handler to fetch all pages const paginatedResponse = await fetchAllPages(context.axios, '/users', params, { maxItems: normalized.limit > 100 ? normalized.limit : undefined, onPageFetched: (_pageData, _pageNum, _totalSoFar) => { // Progress tracking for paginated users fetching }, }); const result = { data: paginatedResponse.data, links: paginatedResponse.links, meta: { ...paginatedResponse.meta, totalFetched: paginatedResponse.data.length, }, }; // Apply detail level filtering after fetching all data if (!normalized.includeSubData && result.data) { result.data = filterArrayByDetailLevel(result.data, 'user', normalized.detail); } // Apply client-side limit after filtering (if requested limit < total available) if (normalized.limit && normalized.limit < result.data.length) { result.data = result.data.slice(normalized.startWith || 0, (normalized.startWith || 0) + normalized.limit); } return { content: [ { type: 'text', text: formatResponse(result), }, ], }; }, args.instance, args.workspaceId); } async function getUser(args) { return await withContext(async (context) => { const normalizedParams = normalizeGetParams(args); const response = await context.axios.get(`/users/${args.id}`); let result = response.data; // Apply detail level filtering if (!normalizedParams.includeSubData) { result = filterByDetailLevel(result, 'user', normalizedParams.detail); } return { content: [ { type: 'text', text: formatResponse(result), }, ], }; }, args.instance, args.workspaceId); } async function updateUser(args) { return await withContext(async (context) => { const body = {}; if (args.name) body.name = args.name; if (args.role) body.role = args.role; if (args.companyId) body.company = { id: args.companyId }; if (args.externalId) body.externalId = args.externalId; const response = await context.axios.patch(`/users/${args.id}`, { data: body, }); return { content: [ { type: 'text', text: formatResponse({ success: true, user: response.data, }), }, ], }; }, args.instance, args.workspaceId); } async function deleteUser(args) { return await withContext(async (context) => { await context.axios.delete(`/users/${args.id}`); return { content: [ { type: 'text', text: formatResponse({ success: true, message: `User ${args.id} deleted successfully`, }), }, ], }; }, args.instance, args.workspaceId); }