list_project_users
Retrieve user IDs and usernames for a Kanboard project. Use returned user IDs to assign tasks or add comments.
Instructions
List the members of a Kanboard project (user_id + username pairs). Provide project_id or project_identifier, or configure .kanboard.yaml in your project root. Works for any user who can see the project — does not require admin permissions. Use the returned user_ids to assign tasks (create_task owner_id), add comments, etc.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_id | No | Numeric project id. Falls back to .kanboard.yaml when omitted. | |
| project_identifier | No | Short project identifier. Falls back to .kanboard.yaml when omitted. |
Implementation Reference
- src/tools/list-project-users.ts:62-86 (handler)The main tool definition for 'list_project_users'. It defines the tool object with name, description, inputSchema, and the async handler that parses input, resolves project context, calls handler.getProjectUsers(), and returns the result.
export const listProjectUsersTool = { name: "list_project_users", description: "List the members of a Kanboard project (user_id + username pairs). " + "Provide project_id or project_identifier, or configure .kanboard.yaml in your project root. " + "Works for any user who can see the project — does not require admin permissions. " + "Use the returned user_ids to assign tasks (create_task owner_id), add comments, etc.", inputSchema: ListProjectUsersInput, handler: async (raw: unknown, deps: ToolDeps): Promise<ListProjectUsersResult> => { const input = ListProjectUsersInput.parse(raw); const ctx = await resolveProjectContext(deps.handler, { ...(input.project_id !== undefined ? { explicitProjectId: input.project_id } : {}), ...(input.project_identifier !== undefined ? { explicitProjectIdentifier: input.project_identifier } : {}), }); const users = await deps.handler.getProjectUsers(ctx.projectId); return { content: [{ type: "text", text: JSON.stringify(users, null, 2) }], structuredContent: { users }, }; }, }; - Input schema (Zod) for the tool: optional project_id (positive int) and project_identifier (string). Validates and types the incoming parameters.
export const ListProjectUsersInput = z .object({ project_id: z .number() .int() .positive() .optional() .describe("Numeric project id. Falls back to .kanboard.yaml when omitted."), project_identifier: z .string() .min(1) .optional() .describe("Short project identifier. Falls back to .kanboard.yaml when omitted."), }) .strict(); - src/tools/index.ts:215-233 (registration)registerTools() iterates over allTools array and calls server.registerTool() for each tool, including listProjectUsersTool. This is how the tool gets mounted on the MCP server.
export function registerTools(server: McpServer, deps: ToolDeps): void { for (const tool of allTools) { // Cast: each tool handler returns a `{ content, structuredContent }` object // that satisfies `CallToolResult`. We use `unknown` in `ToolDef.handler` to // keep the per-tool return types encapsulated, so we cast here at the // registration boundary where the MCP SDK takes ownership. const cb = ((args: Record<string, unknown>) => tool.handler(args, deps)) as unknown as ToolCallback; server.registerTool( tool.name, { description: tool.description, inputSchema: tool.inputSchema, }, cb, ); } } - src/handler/kanboard.ts:862-903 (helper)KanboardHandler.getProjectUsers() — the underlying handler method that calls the Kanboard API via #apiClient.call('getProjectUsers'), normalizes the sparse dict response into ProjectMember[] sorted by user_id.
public async getProjectUsers(projectId: number): Promise<ProjectMember[]> { const raw = await this.#apiClient.call("getProjectUsers", { project_id: projectId }); this.#logger.debug({ method: "getProjectUsers", projectId }, "getProjectUsers OK"); if (raw === false || raw === null || raw === undefined) { throw new KanboardApiError( "getProjectUsers", `getProjectUsers failed for project ${String(projectId)}`, ); } if (typeof raw !== "object" || Array.isArray(raw)) { throw new KanboardApiError( "getProjectUsers", `getProjectUsers: expected dict, got ${Array.isArray(raw) ? "array" : typeof raw}`, ); } const dict = raw as Record<string, unknown>; const members: ProjectMember[] = []; for (const [key, value] of Object.entries(dict)) { const userId = Number(key); if (!Number.isFinite(userId) || userId <= 0) { this.#logger.warn( { method: "getProjectUsers", projectId, key }, "getProjectUsers: dropping malformed user_id key", ); continue; } if (typeof value !== "string") { this.#logger.warn( { method: "getProjectUsers", projectId, key, valueType: typeof value }, "getProjectUsers: dropping non-string username", ); continue; } members.push({ user_id: userId, username: value }); } members.sort((a, b) => a.user_id - b.user_id); return members; } - src/shared/types.ts:279-282 (helper)ProjectMember interface: the typed return shape with user_id (number) and username (string). Used by the tool's structuredContent response.
export interface ProjectMember { user_id: number; username: string; }