feat: add MCP server for AI assistant integration (#5882)

This commit is contained in:
Thomas authored and GitHub committed 2026-06-10 18:28:34 +02:00
1 parent d77c6f8d4e
commit 6baff7be3d
47 files changed
+5722 -12624

No files matched your search

+143 -16
View File
@@ -144,7 +144,19 @@ export const userRouter = createTRPCRouter({
}),
create: permissionRequiredProcedure
.requiresPermission("admin")
.meta({ openapi: { method: "POST", path: "/api/users", tags: ["users"], protect: true } })
.meta({
openapi: {
method: "POST",
path: "/api/users",
tags: ["users"],
protect: true,
},
mcp: {
enabled: true,
description:
"Create a new user account (admin only). REQUIRED: username (string), password (string), confirmPassword (must match password). OPTIONAL: email (string or null)",
},
})
.input(convertIntersectionToZodObject(userCreateSchema))
.output(z.void())
.mutation(async ({ ctx, input }) => {
@@ -159,7 +171,14 @@ export const userRouter = createTRPCRouter({
}),
setProfileImage: protectedProcedure
.output(z.void())
.meta({ openapi: { method: "PUT", path: "/api/users/profileImage", tags: ["users"], protect: true } })
.meta({
openapi: {
method: "PUT",
path: "/api/users/profileImage",
tags: ["users"],
protect: true,
},
})
.input(
z.object({
userId: z.string(),
@@ -205,8 +224,26 @@ export const userRouter = createTRPCRouter({
getAll: permissionRequiredProcedure
.requiresPermission("admin")
.input(z.void())
.output(z.array(selectUserSchema.pick({ id: true, name: true, email: true, emailVerified: true, image: true })))
.meta({ openapi: { method: "GET", path: "/api/users", tags: ["users"], protect: true } })
.output(
z.array(
selectUserSchema.pick({
id: true,
name: true,
email: true,
emailVerified: true,
image: true,
}),
),
)
.meta({
openapi: {
method: "GET",
path: "/api/users",
tags: ["users"],
protect: true,
},
mcp: { enabled: true, description: "List all users (admin only)" },
})
.query(({ ctx }) => {
return ctx.db.query.users.findMany({
columns: {
@@ -221,8 +258,24 @@ export const userRouter = createTRPCRouter({
// Is protected because also used in board access / integration access forms
selectable: protectedProcedure
.input(z.object({ excludeExternalProviders: z.boolean().default(false) }).optional())
.output(z.array(selectUserSchema.pick({ id: true, name: true, image: true, email: true })))
.meta({ openapi: { method: "GET", path: "/api/users/selectable", tags: ["users"], protect: true } })
.output(
z.array(
selectUserSchema.pick({
id: true,
name: true,
image: true,
email: true,
}),
),
)
.meta({
openapi: {
method: "GET",
path: "/api/users/selectable",
tags: ["users"],
protect: true,
},
})
.query(({ ctx, input }) => {
return ctx.db.query.users.findMany({
columns: {
@@ -242,8 +295,24 @@ export const userRouter = createTRPCRouter({
limit: z.number().min(1).max(100).default(10),
}),
)
.output(z.array(selectUserSchema.pick({ id: true, name: true, image: true, email: true })))
.meta({ openapi: { method: "POST", path: "/api/users/search", tags: ["users"], protect: true } })
.output(
z.array(
selectUserSchema.pick({
id: true,
name: true,
image: true,
email: true,
}),
),
)
.meta({
openapi: {
method: "POST",
path: "/api/users/search",
tags: ["users"],
protect: true,
},
})
.query(async ({ input, ctx }) => {
const dbUsers = await ctx.db.query.users.findMany({
columns: {
@@ -283,7 +352,15 @@ export const userRouter = createTRPCRouter({
completedBoardTour: true,
}),
)
.meta({ openapi: { method: "GET", path: "/api/users/{userId}", tags: ["users"], protect: true } })
.meta({
openapi: {
method: "GET",
path: "/api/users/{userId}",
tags: ["users"],
protect: true,
},
mcp: { enabled: true, description: "Get user details by user ID. REQUIRED: userId (string)" },
})
.query(async ({ input, ctx }) => {
// Only admins can view other users details
if (ctx.session.user.id !== input.userId && !ctx.session.user.permissions.includes("admin")) {
@@ -325,7 +402,14 @@ export const userRouter = createTRPCRouter({
editProfile: protectedProcedure
.input(userEditProfileSchema)
.output(z.void())
.meta({ openapi: { method: "PUT", path: "/api/users/profile", tags: ["users"], protect: true } })
.meta({
openapi: {
method: "PUT",
path: "/api/users/profile",
tags: ["users"],
protect: true,
},
})
.mutation(async ({ input, ctx }) => {
// Only admins can view other users details
if (ctx.session.user.id !== input.id && !ctx.session.user.permissions.includes("admin")) {
@@ -369,7 +453,15 @@ export const userRouter = createTRPCRouter({
delete: protectedProcedure
.input(z.object({ userId: z.string() }))
.output(z.void())
.meta({ openapi: { method: "DELETE", path: "/api/users/{userId}", tags: ["users"], protect: true } })
.meta({
openapi: {
method: "DELETE",
path: "/api/users/{userId}",
tags: ["users"],
protect: true,
},
mcp: { enabled: true, description: "Delete a user by ID. REQUIRED: userId (string)" },
})
.mutation(async ({ input, ctx }) => {
// Only admins and user itself can delete a user
if (ctx.session.user.id !== input.userId && !ctx.session.user.permissions.includes("admin")) {
@@ -384,7 +476,14 @@ export const userRouter = createTRPCRouter({
changePassword: protectedProcedure
.input(convertIntersectionToZodObject(userChangePasswordApiSchema))
.output(z.void())
.meta({ openapi: { method: "PATCH", path: "/api/users/{userId}/changePassword", tags: ["users"], protect: true } })
.meta({
openapi: {
method: "PATCH",
path: "/api/users/{userId}/changePassword",
tags: ["users"],
protect: true,
},
})
.mutation(async ({ ctx, input }) => {
const user = ctx.session.user;
// Only admins can change other users' passwords
@@ -449,7 +548,14 @@ export const userRouter = createTRPCRouter({
changeHomeBoards: protectedProcedure
.input(convertIntersectionToZodObject(userChangeHomeBoardsSchema.and(z.object({ userId: z.string() }))))
.output(z.void())
.meta({ openapi: { method: "PATCH", path: "/api/users/changeHome", tags: ["users"], protect: true } })
.meta({
openapi: {
method: "PATCH",
path: "/api/users/changeHome",
tags: ["users"],
protect: true,
},
})
.mutation(async ({ input, ctx }) => {
const user = ctx.session.user;
// Only admins can change other users passwords
@@ -515,14 +621,28 @@ export const userRouter = createTRPCRouter({
changeSearchPreferences: protectedProcedure
.input(convertIntersectionToZodObject(changeSearchPreferencesInputSchema))
.output(z.void())
.meta({ openapi: { method: "PATCH", path: "/api/users/search-preferences", tags: ["users"], protect: true } })
.meta({
openapi: {
method: "PATCH",
path: "/api/users/search-preferences",
tags: ["users"],
protect: true,
},
})
.mutation(async ({ input, ctx }) => {
await changeSearchPreferencesAsync(ctx.db, ctx.session, input);
}),
changeColorScheme: protectedProcedure
.input(userChangeColorSchemeSchema)
.output(z.void())
.meta({ openapi: { method: "PATCH", path: "/api/users/changeScheme", tags: ["users"], protect: true } })
.meta({
openapi: {
method: "PATCH",
path: "/api/users/changeScheme",
tags: ["users"],
protect: true,
},
})
.mutation(async ({ input, ctx }) => {
await ctx.db
.update(users)
@@ -579,7 +699,14 @@ export const userRouter = createTRPCRouter({
changeFirstDayOfWeek: protectedProcedure
.input(convertIntersectionToZodObject(userFirstDayOfWeekSchema.and(byIdSchema)))
.output(z.void())
.meta({ openapi: { method: "PATCH", path: "/api/users/firstDayOfWeek", tags: ["users"], protect: true } })
.meta({
openapi: {
method: "PATCH",
path: "/api/users/firstDayOfWeek",
tags: ["users"],
protect: true,
},
})
.mutation(async ({ input, ctx }) => {
// Only admins can change other users first day of week
if (!ctx.session.user.permissions.includes("admin") && ctx.session.user.id !== input.id) {