@headless-lms/server 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/app/community-mail.d.ts +10 -10
- package/dist/app/community-mail.js +72 -28
- package/dist/app/community-mail.js.map +1 -1
- package/dist/app/container.d.ts +48 -10
- package/dist/app/container.js +268 -48
- package/dist/app/container.js.map +1 -1
- package/dist/app/notification-channels.d.ts +47 -0
- package/dist/app/notification-channels.js +175 -0
- package/dist/app/notification-channels.js.map +1 -0
- package/dist/app/notification-reads.d.ts +23 -0
- package/dist/app/notification-reads.js +74 -0
- package/dist/app/notification-reads.js.map +1 -0
- package/dist/app/platform.d.ts +14 -0
- package/dist/app/platform.js +14 -0
- package/dist/app/platform.js.map +1 -0
- package/dist/http/config.d.ts +4 -0
- package/dist/http/errors.d.ts +1 -0
- package/dist/http/errors.js +24 -4
- package/dist/http/errors.js.map +1 -1
- package/dist/http/fastify.d.ts +8 -0
- package/dist/http/mcp/catalogue.d.ts +11 -9
- package/dist/http/mcp/catalogue.js +15 -21
- package/dist/http/mcp/catalogue.js.map +1 -1
- package/dist/http/mcp/challenge.js +3 -3
- package/dist/http/mcp/challenge.js.map +1 -1
- package/dist/http/mcp/route.js +7 -7
- package/dist/http/mcp/route.js.map +1 -1
- package/dist/http/mcp/test-support.d.ts +6 -5
- package/dist/http/mcp/test-support.js +32 -11
- package/dist/http/mcp/test-support.js.map +1 -1
- package/dist/http/mcp/tool.d.ts +3 -9
- package/dist/http/mcp/tool.js +14 -6
- package/dist/http/mcp/tool.js.map +1 -1
- package/dist/http/mcp/tools/automations.js +10 -20
- package/dist/http/mcp/tools/automations.js.map +1 -1
- package/dist/http/mcp/tools/catalog.js +25 -42
- package/dist/http/mcp/tools/catalog.js.map +1 -1
- package/dist/http/mcp/tools/comments.js +9 -19
- package/dist/http/mcp/tools/comments.js.map +1 -1
- package/dist/http/mcp/tools/communities.js +16 -31
- package/dist/http/mcp/tools/communities.js.map +1 -1
- package/dist/http/mcp/tools/community-members.js +9 -16
- package/dist/http/mcp/tools/community-members.js.map +1 -1
- package/dist/http/mcp/tools/community-moderation.js +13 -25
- package/dist/http/mcp/tools/community-moderation.js.map +1 -1
- package/dist/http/mcp/tools/courses.js +11 -21
- package/dist/http/mcp/tools/courses.js.map +1 -1
- package/dist/http/mcp/tools/events.d.ts +6 -0
- package/dist/http/mcp/tools/events.js +170 -0
- package/dist/http/mcp/tools/events.js.map +1 -0
- package/dist/http/mcp/tools/index.d.ts +5 -1
- package/dist/http/mcp/tools/index.js +45 -10
- package/dist/http/mcp/tools/index.js.map +1 -1
- package/dist/http/mcp/tools/organization.d.ts +6 -0
- package/dist/http/mcp/tools/organization.js +214 -0
- package/dist/http/mcp/tools/organization.js.map +1 -0
- package/dist/http/mcp/tools/people.js +30 -49
- package/dist/http/mcp/tools/people.js.map +1 -1
- package/dist/http/plugins/auth.js +37 -8
- package/dist/http/plugins/auth.js.map +1 -1
- package/dist/http/plugins/error-handler.d.ts +5 -1
- package/dist/http/plugins/error-handler.js +13 -3
- package/dist/http/plugins/error-handler.js.map +1 -1
- package/dist/http/plugins/openapi.js +18 -2
- package/dist/http/plugins/openapi.js.map +1 -1
- package/dist/http/plugins/platform.d.ts +7 -0
- package/dist/http/plugins/platform.js +13 -0
- package/dist/http/plugins/platform.js.map +1 -0
- package/dist/http/routes/access.js +54 -36
- package/dist/http/routes/access.js.map +1 -1
- package/dist/http/routes/activities.js +45 -45
- package/dist/http/routes/activities.js.map +1 -1
- package/dist/http/routes/assets.js +30 -65
- package/dist/http/routes/assets.js.map +1 -1
- package/dist/http/routes/automations.js +37 -42
- package/dist/http/routes/automations.js.map +1 -1
- package/dist/http/routes/bundles.js +33 -36
- package/dist/http/routes/bundles.js.map +1 -1
- package/dist/http/routes/communities.js +144 -136
- package/dist/http/routes/communities.js.map +1 -1
- package/dist/http/routes/community-moderation.js +83 -137
- package/dist/http/routes/community-moderation.js.map +1 -1
- package/dist/http/routes/course-analytics.js +32 -15
- package/dist/http/routes/course-analytics.js.map +1 -1
- package/dist/http/routes/courses.js +25 -32
- package/dist/http/routes/courses.js.map +1 -1
- package/dist/http/routes/dashboard.js +9 -9
- package/dist/http/routes/dashboard.js.map +1 -1
- package/dist/http/routes/discussion.js +21 -39
- package/dist/http/routes/discussion.js.map +1 -1
- package/dist/http/routes/downloads.js +41 -47
- package/dist/http/routes/downloads.js.map +1 -1
- package/dist/http/routes/entitlements.js +19 -23
- package/dist/http/routes/entitlements.js.map +1 -1
- package/dist/http/routes/events.d.ts +7 -0
- package/dist/http/routes/events.js +333 -0
- package/dist/http/routes/events.js.map +1 -0
- package/dist/http/routes/integrations.js +40 -73
- package/dist/http/routes/integrations.js.map +1 -1
- package/dist/http/routes/member/assets.js +10 -13
- package/dist/http/routes/member/assets.js.map +1 -1
- package/dist/http/routes/member/chats.js +177 -75
- package/dist/http/routes/member/chats.js.map +1 -1
- package/dist/http/routes/member/comments.js +65 -26
- package/dist/http/routes/member/comments.js.map +1 -1
- package/dist/http/routes/member/community.js +68 -51
- package/dist/http/routes/member/community.js.map +1 -1
- package/dist/http/routes/member/courses.js +12 -19
- package/dist/http/routes/member/courses.js.map +1 -1
- package/dist/http/routes/member/devices.d.ts +7 -0
- package/dist/http/routes/member/devices.js +97 -0
- package/dist/http/routes/member/devices.js.map +1 -0
- package/dist/http/routes/member/downloads.js +6 -9
- package/dist/http/routes/member/downloads.js.map +1 -1
- package/dist/http/routes/member/events.d.ts +7 -0
- package/dist/http/routes/member/events.js +386 -0
- package/dist/http/routes/member/events.js.map +1 -0
- package/dist/http/routes/member/files.js +71 -46
- package/dist/http/routes/member/files.js.map +1 -1
- package/dist/http/routes/member/index.js +6 -0
- package/dist/http/routes/member/index.js.map +1 -1
- package/dist/http/routes/member/notification-settings.d.ts +7 -0
- package/dist/http/routes/member/notification-settings.js +100 -0
- package/dist/http/routes/member/notification-settings.js.map +1 -0
- package/dist/http/routes/member/notifications.js +69 -45
- package/dist/http/routes/member/notifications.js.map +1 -1
- package/dist/http/routes/member/org.js +5 -15
- package/dist/http/routes/member/org.js.map +1 -1
- package/dist/http/routes/member/posts.js +94 -95
- package/dist/http/routes/member/posts.js.map +1 -1
- package/dist/http/routes/member/profile.js +15 -6
- package/dist/http/routes/member/profile.js.map +1 -1
- package/dist/http/routes/member/progress.js +7 -11
- package/dist/http/routes/member/progress.js.map +1 -1
- package/dist/http/routes/organizations.js +415 -110
- package/dist/http/routes/organizations.js.map +1 -1
- package/dist/http/routes/platform.d.ts +7 -0
- package/dist/http/routes/platform.js +61 -0
- package/dist/http/routes/platform.js.map +1 -0
- package/dist/http/routes/students.js +12 -75
- package/dist/http/routes/students.js.map +1 -1
- package/dist/http/routes/webhooks.js +30 -39
- package/dist/http/routes/webhooks.js.map +1 -1
- package/dist/http/routes.js +9 -0
- package/dist/http/routes.js.map +1 -1
- package/dist/http/schemas/access.d.ts +346 -64
- package/dist/http/schemas/access.js +39 -12
- package/dist/http/schemas/access.js.map +1 -1
- package/dist/http/schemas/assets.d.ts +13 -8
- package/dist/http/schemas/assets.js +6 -5
- package/dist/http/schemas/assets.js.map +1 -1
- package/dist/http/schemas/automations.d.ts +4 -8
- package/dist/http/schemas/chats.d.ts +238 -36
- package/dist/http/schemas/chats.js +14 -7
- package/dist/http/schemas/chats.js.map +1 -1
- package/dist/http/schemas/community.d.ts +140 -33
- package/dist/http/schemas/community.js +25 -9
- package/dist/http/schemas/community.js.map +1 -1
- package/dist/http/schemas/course-analytics.d.ts +51 -1
- package/dist/http/schemas/course-analytics.js +19 -1
- package/dist/http/schemas/course-analytics.js.map +1 -1
- package/dist/http/schemas/dashboard.d.ts +1 -0
- package/dist/http/schemas/dashboard.js +2 -1
- package/dist/http/schemas/dashboard.js.map +1 -1
- package/dist/http/schemas/discussion.d.ts +45 -73
- package/dist/http/schemas/discussion.js +16 -4
- package/dist/http/schemas/discussion.js.map +1 -1
- package/dist/http/schemas/entitlements.d.ts +9 -12
- package/dist/http/schemas/entitlements.js +11 -5
- package/dist/http/schemas/entitlements.js.map +1 -1
- package/dist/http/schemas/events.d.ts +527 -0
- package/dist/http/schemas/events.js +64 -0
- package/dist/http/schemas/events.js.map +1 -0
- package/dist/http/schemas/files.d.ts +15 -5
- package/dist/http/schemas/files.js +10 -2
- package/dist/http/schemas/files.js.map +1 -1
- package/dist/http/schemas/index.d.ts +14 -12
- package/dist/http/schemas/index.js +14 -12
- package/dist/http/schemas/invites.d.ts +3 -47
- package/dist/http/schemas/invites.js +4 -25
- package/dist/http/schemas/invites.js.map +1 -1
- package/dist/http/schemas/member.d.ts +94 -7
- package/dist/http/schemas/member.js +7 -3
- package/dist/http/schemas/member.js.map +1 -1
- package/dist/http/schemas/members.d.ts +329 -65
- package/dist/http/schemas/members.js +67 -25
- package/dist/http/schemas/members.js.map +1 -1
- package/dist/http/schemas/notifications.d.ts +502 -175
- package/dist/http/schemas/notifications.js +148 -28
- package/dist/http/schemas/notifications.js.map +1 -1
- package/dist/http/schemas/platform.d.ts +37 -0
- package/dist/http/schemas/platform.js +19 -0
- package/dist/http/schemas/platform.js.map +1 -0
- package/dist/http/schemas/posts.d.ts +92 -38
- package/dist/http/schemas/posts.js +4 -4
- package/dist/http/schemas/posts.js.map +1 -1
- package/dist/http/schemas/profile.d.ts +1 -1
- package/dist/http/schemas/shared.d.ts +10 -13
- package/dist/http/schemas/shared.js +3 -11
- package/dist/http/schemas/shared.js.map +1 -1
- package/dist/http/schemas/students.d.ts +22 -0
- package/dist/http/schemas/students.js +3 -1
- package/dist/http/schemas/students.js.map +1 -1
- package/dist/http/scope.d.ts +9 -12
- package/dist/http/scope.js +30 -35
- package/dist/http/scope.js.map +1 -1
- package/dist/http/server.js +9 -0
- package/dist/http/server.js.map +1 -1
- package/dist/http/shutdown.d.ts +11 -0
- package/dist/http/shutdown.js +40 -0
- package/dist/http/shutdown.js.map +1 -0
- package/dist/http/sse.d.ts +19 -0
- package/dist/http/sse.js +104 -0
- package/dist/http/sse.js.map +1 -0
- package/dist/http/test-caller.d.ts +13 -0
- package/dist/http/test-caller.js +14 -0
- package/dist/http/test-caller.js.map +1 -0
- package/dist/http/well-known.js +2 -2
- package/dist/http/well-known.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/packages/server/package.js +1 -1
- package/package.json +7 -6
- package/dist/http/routes/member/actor.d.ts +0 -14
- package/dist/http/routes/member/actor.js +0 -11
- package/dist/http/routes/member/actor.js.map +0 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { AssignmentList, ContentIdParam, CreateRole, MembersPage, MembersQuery, OrgRoleList, OrgRoleView, OrgUserContentParam, OrgUserIdParam, RoleKeyParam, SetRole, UpdateRole } from "../../schemas/members.js";
|
|
2
|
+
import { Organization, UpdateOrganization } from "../../schemas/organizations.js";
|
|
3
|
+
import { CreateInvite, Invite } from "../../schemas/invites.js";
|
|
4
|
+
import "../../schemas/index.js";
|
|
5
|
+
import { defineTool, noInput } from "../tool.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
//#region src/http/mcp/tools/organization.ts
|
|
8
|
+
const organizationTools = [
|
|
9
|
+
defineTool({
|
|
10
|
+
name: "update_organization",
|
|
11
|
+
title: "Update organization",
|
|
12
|
+
description: "Change the organization's name or slug.",
|
|
13
|
+
kind: "write",
|
|
14
|
+
idempotent: true,
|
|
15
|
+
openWorld: false,
|
|
16
|
+
input: UpdateOrganization,
|
|
17
|
+
output: Organization,
|
|
18
|
+
call: async (input, { container, caller }) => {
|
|
19
|
+
const org = await container.organizations.updateOrganization(input, caller.actor);
|
|
20
|
+
return {
|
|
21
|
+
id: org.id,
|
|
22
|
+
name: org.name,
|
|
23
|
+
slug: org.slug,
|
|
24
|
+
createdAt: org.createdAt.toISOString()
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}),
|
|
28
|
+
defineTool({
|
|
29
|
+
name: "list_roles",
|
|
30
|
+
title: "List roles",
|
|
31
|
+
description: "The organization's roles with their levels, permissions and how many people hold each.",
|
|
32
|
+
kind: "read",
|
|
33
|
+
idempotent: true,
|
|
34
|
+
openWorld: false,
|
|
35
|
+
input: noInput,
|
|
36
|
+
output: z.object({ roles: OrgRoleList }),
|
|
37
|
+
call: async (_input, { container, caller }) => ({ roles: await container.organizations.listRoles(caller.actor) })
|
|
38
|
+
}),
|
|
39
|
+
defineTool({
|
|
40
|
+
name: "get_role",
|
|
41
|
+
title: "Get role",
|
|
42
|
+
description: "One role with its levels and permissions.",
|
|
43
|
+
kind: "read",
|
|
44
|
+
idempotent: true,
|
|
45
|
+
openWorld: false,
|
|
46
|
+
input: RoleKeyParam,
|
|
47
|
+
output: OrgRoleView,
|
|
48
|
+
call: ({ key }, { container, caller }) => container.organizations.getRole(key, caller.actor)
|
|
49
|
+
}),
|
|
50
|
+
defineTool({
|
|
51
|
+
name: "update_role",
|
|
52
|
+
title: "Update role",
|
|
53
|
+
description: "Rename a role, or change its permissions, levels or team flag. Changes need every permission the role holds before and after.",
|
|
54
|
+
kind: "write",
|
|
55
|
+
idempotent: true,
|
|
56
|
+
openWorld: false,
|
|
57
|
+
input: UpdateRole.extend(RoleKeyParam.shape),
|
|
58
|
+
output: OrgRoleView,
|
|
59
|
+
call: ({ key, ...input }, { container, caller }) => container.organizations.updateRole(key, input, caller.actor)
|
|
60
|
+
}),
|
|
61
|
+
defineTool({
|
|
62
|
+
name: "create_role",
|
|
63
|
+
title: "Create role",
|
|
64
|
+
description: "Add a role to the organization, starting from a deployment role (base).",
|
|
65
|
+
kind: "write",
|
|
66
|
+
idempotent: false,
|
|
67
|
+
openWorld: false,
|
|
68
|
+
input: CreateRole,
|
|
69
|
+
output: OrgRoleView,
|
|
70
|
+
call: (input, { container, caller }) => container.organizations.createRole(input, caller.actor)
|
|
71
|
+
}),
|
|
72
|
+
defineTool({
|
|
73
|
+
name: "reset_role",
|
|
74
|
+
title: "Reset role",
|
|
75
|
+
description: "Reset a deployment role to the deployment's version.",
|
|
76
|
+
kind: "destructive",
|
|
77
|
+
idempotent: true,
|
|
78
|
+
openWorld: false,
|
|
79
|
+
input: RoleKeyParam,
|
|
80
|
+
output: OrgRoleView,
|
|
81
|
+
call: ({ key }, { container, caller }) => container.organizations.resetRole(key, caller.actor)
|
|
82
|
+
}),
|
|
83
|
+
defineTool({
|
|
84
|
+
name: "delete_role",
|
|
85
|
+
title: "Delete role",
|
|
86
|
+
description: "Delete a role the organization added. Refused while anyone holds it.",
|
|
87
|
+
kind: "destructive",
|
|
88
|
+
idempotent: false,
|
|
89
|
+
openWorld: false,
|
|
90
|
+
input: RoleKeyParam,
|
|
91
|
+
call: ({ key }, { container, caller }) => container.organizations.deleteRole(key, caller.actor)
|
|
92
|
+
}),
|
|
93
|
+
defineTool({
|
|
94
|
+
name: "list_team",
|
|
95
|
+
title: "List team",
|
|
96
|
+
description: "Everyone with an org role other than Member or a role on a content item, invited people included.",
|
|
97
|
+
kind: "read",
|
|
98
|
+
idempotent: true,
|
|
99
|
+
openWorld: false,
|
|
100
|
+
input: MembersQuery,
|
|
101
|
+
output: MembersPage,
|
|
102
|
+
call: (input, { container, caller }) => container.organizations.listTeam(input, caller.actor)
|
|
103
|
+
}),
|
|
104
|
+
defineTool({
|
|
105
|
+
name: "list_person_roles",
|
|
106
|
+
title: "List a person's roles",
|
|
107
|
+
description: "A person's org role and every role they hold on a content item.",
|
|
108
|
+
kind: "read",
|
|
109
|
+
idempotent: true,
|
|
110
|
+
openWorld: false,
|
|
111
|
+
input: OrgUserIdParam,
|
|
112
|
+
output: z.object({ roles: AssignmentList }),
|
|
113
|
+
call: async ({ orgUserId }, { container, caller }) => ({ roles: await container.organizations.listAssignments(orgUserId, caller.actor) })
|
|
114
|
+
}),
|
|
115
|
+
defineTool({
|
|
116
|
+
name: "list_roles_on_content",
|
|
117
|
+
title: "List roles on content",
|
|
118
|
+
description: "The people with a role on one course, download, community or event.",
|
|
119
|
+
kind: "read",
|
|
120
|
+
idempotent: true,
|
|
121
|
+
openWorld: false,
|
|
122
|
+
input: ContentIdParam,
|
|
123
|
+
output: z.object({ roles: AssignmentList }),
|
|
124
|
+
call: async ({ contentId }, { container, caller }) => ({ roles: await container.organizations.listAssignmentsOn(contentId, caller.actor) })
|
|
125
|
+
}),
|
|
126
|
+
defineTool({
|
|
127
|
+
name: "set_org_role",
|
|
128
|
+
title: "Set org role",
|
|
129
|
+
description: "Set a person's org role. Needs every permission the old and the new role hold.",
|
|
130
|
+
kind: "write",
|
|
131
|
+
idempotent: true,
|
|
132
|
+
openWorld: false,
|
|
133
|
+
input: SetRole.extend(OrgUserIdParam.shape),
|
|
134
|
+
output: z.object({ roles: AssignmentList }),
|
|
135
|
+
call: async ({ orgUserId, roleKey }, { container, caller }) => ({ roles: await container.organizations.setOrgRole(orgUserId, roleKey, caller.actor) })
|
|
136
|
+
}),
|
|
137
|
+
defineTool({
|
|
138
|
+
name: "assign_role",
|
|
139
|
+
title: "Give a role on content",
|
|
140
|
+
description: "Give a person a role on one course, download, community or event. The role must list the item's type.",
|
|
141
|
+
kind: "write",
|
|
142
|
+
idempotent: true,
|
|
143
|
+
openWorld: false,
|
|
144
|
+
input: SetRole.extend(OrgUserContentParam.shape),
|
|
145
|
+
output: z.object({ roles: AssignmentList }),
|
|
146
|
+
call: async ({ orgUserId, contentId, roleKey }, { container, caller }) => ({ roles: await container.organizations.assignRole(orgUserId, contentId, roleKey, caller.actor) })
|
|
147
|
+
}),
|
|
148
|
+
defineTool({
|
|
149
|
+
name: "unassign_role",
|
|
150
|
+
title: "Remove a role on content",
|
|
151
|
+
description: "Remove a person's role on one course, download, community or event.",
|
|
152
|
+
kind: "destructive",
|
|
153
|
+
idempotent: true,
|
|
154
|
+
openWorld: false,
|
|
155
|
+
input: OrgUserContentParam,
|
|
156
|
+
output: z.object({ roles: AssignmentList }),
|
|
157
|
+
call: async ({ orgUserId, contentId }, { container, caller }) => ({ roles: await container.organizations.unassignRole(orgUserId, contentId, caller.actor) })
|
|
158
|
+
}),
|
|
159
|
+
defineTool({
|
|
160
|
+
name: "invite_person",
|
|
161
|
+
title: "Invite person",
|
|
162
|
+
description: "Invite someone by email with an org role (default member). They exist as an invited person at once.",
|
|
163
|
+
kind: "write",
|
|
164
|
+
idempotent: false,
|
|
165
|
+
openWorld: true,
|
|
166
|
+
input: CreateInvite,
|
|
167
|
+
output: Invite,
|
|
168
|
+
call: async (input, { container, caller }) => {
|
|
169
|
+
const invite = await container.organizations.createInvite(input, caller.actor, caller.userId);
|
|
170
|
+
return {
|
|
171
|
+
id: invite.id,
|
|
172
|
+
email: invite.email,
|
|
173
|
+
roleKey: invite.roleKey,
|
|
174
|
+
status: invite.status,
|
|
175
|
+
expiresAt: invite.expiresAt,
|
|
176
|
+
createdAt: invite.createdAt
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}),
|
|
180
|
+
defineTool({
|
|
181
|
+
name: "resend_invite",
|
|
182
|
+
title: "Resend invite",
|
|
183
|
+
description: "Send a person's pending invite email again, with a new link.",
|
|
184
|
+
kind: "write",
|
|
185
|
+
idempotent: false,
|
|
186
|
+
openWorld: true,
|
|
187
|
+
input: OrgUserIdParam,
|
|
188
|
+
call: ({ orgUserId }, { container, caller }) => container.organizations.resendInvite(orgUserId, caller.actor, caller.userId)
|
|
189
|
+
}),
|
|
190
|
+
defineTool({
|
|
191
|
+
name: "cancel_invite",
|
|
192
|
+
title: "Cancel invite",
|
|
193
|
+
description: "Cancel a person's pending invite.",
|
|
194
|
+
kind: "destructive",
|
|
195
|
+
idempotent: false,
|
|
196
|
+
openWorld: false,
|
|
197
|
+
input: OrgUserIdParam,
|
|
198
|
+
call: ({ orgUserId }, { container, caller }) => container.organizations.cancelInvite(orgUserId, caller.actor)
|
|
199
|
+
}),
|
|
200
|
+
defineTool({
|
|
201
|
+
name: "remove_person",
|
|
202
|
+
title: "Remove person",
|
|
203
|
+
description: "Remove a person from the organization, with their API keys and connected apps here. Needs every permission they hold.",
|
|
204
|
+
kind: "destructive",
|
|
205
|
+
idempotent: false,
|
|
206
|
+
openWorld: false,
|
|
207
|
+
input: OrgUserIdParam,
|
|
208
|
+
call: ({ orgUserId }, { container, caller }) => container.organizations.removePerson(orgUserId, caller.actor)
|
|
209
|
+
})
|
|
210
|
+
];
|
|
211
|
+
//#endregion
|
|
212
|
+
export { organizationTools };
|
|
213
|
+
|
|
214
|
+
//# sourceMappingURL=organization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization.js","names":[],"sources":["../../../../src/http/mcp/tools/organization.ts"],"sourcesContent":["import { z } from 'zod';\nimport {\n AssignmentList,\n ContentIdParam,\n CreateInvite,\n CreateRole,\n Invite,\n MembersPage,\n MembersQuery,\n OrgRoleList,\n OrgRoleView,\n OrgUserContentParam,\n OrgUserIdParam,\n Organization,\n RoleKeyParam,\n SetRole,\n UpdateOrganization,\n UpdateRole,\n} from '../../schemas/index.js';\nimport { defineTool, noInput, type Tool } from '../tool.js';\n\nexport const organizationTools: Tool[] = [\n defineTool({\n name: 'update_organization',\n title: 'Update organization',\n description: \"Change the organization's name or slug.\",\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: UpdateOrganization,\n output: Organization,\n call: async (input, { container, caller }) => {\n const org = await container.organizations.updateOrganization(input, caller.actor);\n return { id: org.id, name: org.name, slug: org.slug, createdAt: org.createdAt.toISOString() };\n },\n }),\n defineTool({\n name: 'list_roles',\n title: 'List roles',\n description:\n \"The organization's roles with their levels, permissions and how many people hold each.\",\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: noInput,\n output: z.object({ roles: OrgRoleList }),\n call: async (_input, { container, caller }) => ({\n roles: await container.organizations.listRoles(caller.actor),\n }),\n }),\n defineTool({\n name: 'get_role',\n title: 'Get role',\n description: 'One role with its levels and permissions.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: RoleKeyParam,\n output: OrgRoleView,\n call: ({ key }, { container, caller }) => container.organizations.getRole(key, caller.actor),\n }),\n defineTool({\n name: 'update_role',\n title: 'Update role',\n description:\n 'Rename a role, or change its permissions, levels or team flag. Changes need every permission the role holds before and after.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: UpdateRole.extend(RoleKeyParam.shape),\n output: OrgRoleView,\n call: ({ key, ...input }, { container, caller }) =>\n container.organizations.updateRole(key, input, caller.actor),\n }),\n defineTool({\n name: 'create_role',\n title: 'Create role',\n description: 'Add a role to the organization, starting from a deployment role (base).',\n kind: 'write',\n idempotent: false,\n openWorld: false,\n input: CreateRole,\n output: OrgRoleView,\n call: (input, { container, caller }) => container.organizations.createRole(input, caller.actor),\n }),\n defineTool({\n name: 'reset_role',\n title: 'Reset role',\n description: \"Reset a deployment role to the deployment's version.\",\n kind: 'destructive',\n idempotent: true,\n openWorld: false,\n input: RoleKeyParam,\n output: OrgRoleView,\n call: ({ key }, { container, caller }) => container.organizations.resetRole(key, caller.actor),\n }),\n defineTool({\n name: 'delete_role',\n title: 'Delete role',\n description: 'Delete a role the organization added. Refused while anyone holds it.',\n kind: 'destructive',\n idempotent: false,\n openWorld: false,\n input: RoleKeyParam,\n call: ({ key }, { container, caller }) => container.organizations.deleteRole(key, caller.actor),\n }),\n defineTool({\n name: 'list_team',\n title: 'List team',\n description:\n 'Everyone with an org role other than Member or a role on a content item, invited people included.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: MembersQuery,\n output: MembersPage,\n call: (input, { container, caller }) => container.organizations.listTeam(input, caller.actor),\n }),\n defineTool({\n name: 'list_person_roles',\n title: \"List a person's roles\",\n description: \"A person's org role and every role they hold on a content item.\",\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: OrgUserIdParam,\n output: z.object({ roles: AssignmentList }),\n call: async ({ orgUserId }, { container, caller }) => ({\n roles: await container.organizations.listAssignments(orgUserId, caller.actor),\n }),\n }),\n defineTool({\n name: 'list_roles_on_content',\n title: 'List roles on content',\n description: 'The people with a role on one course, download, community or event.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: ContentIdParam,\n output: z.object({ roles: AssignmentList }),\n call: async ({ contentId }, { container, caller }) => ({\n roles: await container.organizations.listAssignmentsOn(contentId, caller.actor),\n }),\n }),\n defineTool({\n name: 'set_org_role',\n title: 'Set org role',\n description: \"Set a person's org role. Needs every permission the old and the new role hold.\",\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: SetRole.extend(OrgUserIdParam.shape),\n output: z.object({ roles: AssignmentList }),\n call: async ({ orgUserId, roleKey }, { container, caller }) => ({\n roles: await container.organizations.setOrgRole(orgUserId, roleKey, caller.actor),\n }),\n }),\n defineTool({\n name: 'assign_role',\n title: 'Give a role on content',\n description:\n \"Give a person a role on one course, download, community or event. The role must list the item's type.\",\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: SetRole.extend(OrgUserContentParam.shape),\n output: z.object({ roles: AssignmentList }),\n call: async ({ orgUserId, contentId, roleKey }, { container, caller }) => ({\n roles: await container.organizations.assignRole(orgUserId, contentId, roleKey, caller.actor),\n }),\n }),\n defineTool({\n name: 'unassign_role',\n title: 'Remove a role on content',\n description: \"Remove a person's role on one course, download, community or event.\",\n kind: 'destructive',\n idempotent: true,\n openWorld: false,\n input: OrgUserContentParam,\n output: z.object({ roles: AssignmentList }),\n call: async ({ orgUserId, contentId }, { container, caller }) => ({\n roles: await container.organizations.unassignRole(orgUserId, contentId, caller.actor),\n }),\n }),\n defineTool({\n name: 'invite_person',\n title: 'Invite person',\n description:\n 'Invite someone by email with an org role (default member). They exist as an invited person at once.',\n kind: 'write',\n idempotent: false,\n openWorld: true,\n input: CreateInvite,\n output: Invite,\n call: async (input, { container, caller }) => {\n const invite = await container.organizations.createInvite(input, caller.actor, caller.userId);\n return {\n id: invite.id,\n email: invite.email,\n roleKey: invite.roleKey,\n status: invite.status,\n expiresAt: invite.expiresAt,\n createdAt: invite.createdAt,\n };\n },\n }),\n defineTool({\n name: 'resend_invite',\n title: 'Resend invite',\n description: \"Send a person's pending invite email again, with a new link.\",\n kind: 'write',\n idempotent: false,\n openWorld: true,\n input: OrgUserIdParam,\n call: ({ orgUserId }, { container, caller }) =>\n container.organizations.resendInvite(orgUserId, caller.actor, caller.userId),\n }),\n defineTool({\n name: 'cancel_invite',\n title: 'Cancel invite',\n description: \"Cancel a person's pending invite.\",\n kind: 'destructive',\n idempotent: false,\n openWorld: false,\n input: OrgUserIdParam,\n call: ({ orgUserId }, { container, caller }) =>\n container.organizations.cancelInvite(orgUserId, caller.actor),\n }),\n defineTool({\n name: 'remove_person',\n title: 'Remove person',\n description:\n 'Remove a person from the organization, with their API keys and connected apps here. Needs every permission they hold.',\n kind: 'destructive',\n idempotent: false,\n openWorld: false,\n input: OrgUserIdParam,\n call: ({ orgUserId }, { container, caller }) =>\n container.organizations.removePerson(orgUserId, caller.actor),\n }),\n];\n"],"mappings":"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"}
|
|
@@ -1,142 +1,123 @@
|
|
|
1
1
|
import { CourseIdPathParam } from "../../schemas/activities.js";
|
|
2
2
|
import { StudentIdParam, StudentsQuery } from "../../schemas/students.js";
|
|
3
|
-
import {
|
|
3
|
+
import { EntitlementsQuery, GrantEntitlement, RevokeEntitlement } from "../../schemas/entitlements.js";
|
|
4
4
|
import { CreateInvite, Invite } from "../../schemas/invites.js";
|
|
5
5
|
import { EnrollmentSeriesQuery } from "../../schemas/dashboard.js";
|
|
6
|
+
import { CourseLearnersQuery } from "../../schemas/course-analytics.js";
|
|
6
7
|
import "../../schemas/index.js";
|
|
7
|
-
import {
|
|
8
|
+
import { defineTool, noInput } from "../tool.js";
|
|
8
9
|
//#region src/http/mcp/tools/people.ts
|
|
9
10
|
const peopleTools = [
|
|
10
11
|
defineTool({
|
|
11
12
|
name: "list_students",
|
|
12
13
|
title: "List students",
|
|
13
14
|
description: "Search and page through the students with their access count, average progress, status and last activity.",
|
|
14
|
-
scope: "students:read",
|
|
15
15
|
kind: "read",
|
|
16
16
|
idempotent: true,
|
|
17
17
|
openWorld: false,
|
|
18
18
|
input: StudentsQuery,
|
|
19
|
-
call: (input, { container,
|
|
19
|
+
call: (input, { container, caller }) => container.reporting.admin.listStudents(input, caller.actor)
|
|
20
20
|
}),
|
|
21
21
|
defineTool({
|
|
22
22
|
name: "invite_student",
|
|
23
23
|
title: "Invite student",
|
|
24
24
|
description: "Invite someone by email as a student. They exist as an invited student at once. sendEmail false skips the email.",
|
|
25
|
-
scope: "students:invite",
|
|
26
25
|
kind: "write",
|
|
27
26
|
idempotent: false,
|
|
28
27
|
openWorld: true,
|
|
29
|
-
input: CreateInvite.omit({
|
|
28
|
+
input: CreateInvite.omit({ roleKey: true }),
|
|
30
29
|
output: Invite,
|
|
31
|
-
call: (input, { container,
|
|
32
|
-
orgId: scope.orgId,
|
|
33
|
-
inviterUserId: scope.userId,
|
|
34
|
-
role: "student",
|
|
35
|
-
...input
|
|
36
|
-
})
|
|
37
|
-
}),
|
|
38
|
-
defineTool({
|
|
39
|
-
name: "resend_student_invite",
|
|
40
|
-
title: "Resend student invite",
|
|
41
|
-
description: "Send a pending student's invite email again.",
|
|
42
|
-
scope: "students:invite",
|
|
43
|
-
kind: "write",
|
|
44
|
-
idempotent: false,
|
|
45
|
-
openWorld: true,
|
|
46
|
-
input: StudentIdParam,
|
|
47
|
-
call: ({ id }, { container, scope }) => container.organizations.resendStudentInvite(actor(scope), {
|
|
48
|
-
orgId: scope.orgId,
|
|
49
|
-
orgUserId: id,
|
|
50
|
-
inviterUserId: scope.userId
|
|
51
|
-
})
|
|
30
|
+
call: (input, { container, caller }) => container.organizations.createInvite(input, caller.actor, caller.userId)
|
|
52
31
|
}),
|
|
53
32
|
defineTool({
|
|
54
33
|
name: "list_access",
|
|
55
34
|
title: "List access",
|
|
56
35
|
description: "Who has access to what. Filter by student (orgUserId), content, type or status.",
|
|
57
|
-
scope: "access:read",
|
|
58
36
|
kind: "read",
|
|
59
37
|
idempotent: true,
|
|
60
38
|
openWorld: false,
|
|
61
39
|
input: EntitlementsQuery,
|
|
62
|
-
call: (input, { container,
|
|
40
|
+
call: (input, { container, caller }) => container.entitlements.list(input, caller.actor)
|
|
63
41
|
}),
|
|
64
42
|
defineTool({
|
|
65
43
|
name: "grant_access",
|
|
66
44
|
title: "Grant access",
|
|
67
|
-
description: "Give a student access to one course, download or
|
|
68
|
-
scope: "access:write",
|
|
45
|
+
description: "Give a student access to one course, download, community or event (contentId) or to a bundle (bundleId), never both. expiresAt is a date-time, or null for no expiry.",
|
|
69
46
|
kind: "write",
|
|
70
47
|
idempotent: false,
|
|
71
48
|
openWorld: false,
|
|
72
49
|
input: GrantEntitlement,
|
|
73
|
-
call: (input, { container,
|
|
50
|
+
call: (input, { container, caller }) => container.entitlements.grant(input, caller.actor)
|
|
74
51
|
}),
|
|
75
52
|
defineTool({
|
|
76
|
-
name: "
|
|
77
|
-
title: "Revoke
|
|
78
|
-
description: "Revoke
|
|
79
|
-
scope: "access:write",
|
|
53
|
+
name: "revoke_access",
|
|
54
|
+
title: "Revoke access",
|
|
55
|
+
description: "Revoke a student's direct grant to one course, download, community or event (contentId) or to a bundle (bundleId), never both. Access through a bundle stays. grant_access makes it active again.",
|
|
80
56
|
kind: "destructive",
|
|
81
|
-
idempotent:
|
|
57
|
+
idempotent: false,
|
|
82
58
|
openWorld: false,
|
|
83
|
-
input:
|
|
84
|
-
call: (
|
|
59
|
+
input: RevokeEntitlement,
|
|
60
|
+
call: (input, { container, caller }) => container.entitlements.revoke(input, caller.actor)
|
|
85
61
|
}),
|
|
86
62
|
defineTool({
|
|
87
63
|
name: "get_dashboard",
|
|
88
64
|
title: "Get dashboard",
|
|
89
65
|
description: "Published and draft course counts, active students, and access grants expiring soon.",
|
|
90
|
-
scope: "analytics:read",
|
|
91
66
|
kind: "read",
|
|
92
67
|
idempotent: true,
|
|
93
68
|
openWorld: false,
|
|
94
69
|
input: noInput,
|
|
95
|
-
call: (_input, { container,
|
|
70
|
+
call: (_input, { container, caller }) => container.reporting.admin.overview(caller.actor)
|
|
96
71
|
}),
|
|
97
72
|
defineTool({
|
|
98
73
|
name: "get_enrollment_trend",
|
|
99
74
|
title: "Get enrollment trend",
|
|
100
75
|
description: "Daily enrollments across the organization for the last days (default 30, at most 365).",
|
|
101
|
-
scope: "analytics:read",
|
|
102
76
|
kind: "read",
|
|
103
77
|
idempotent: true,
|
|
104
78
|
openWorld: false,
|
|
105
79
|
input: EnrollmentSeriesQuery,
|
|
106
|
-
call: ({ days }, { container,
|
|
80
|
+
call: ({ days }, { container, caller }) => container.reporting.admin.enrollments(days, caller.actor)
|
|
107
81
|
}),
|
|
108
82
|
defineTool({
|
|
109
83
|
name: "get_course_analytics",
|
|
110
84
|
title: "Get course analytics",
|
|
111
85
|
description: "Enrolled, started and completed counts, average progress and per-activity engagement for one course.",
|
|
112
|
-
scope: "analytics:read",
|
|
113
86
|
kind: "read",
|
|
114
87
|
idempotent: true,
|
|
115
88
|
openWorld: false,
|
|
116
89
|
input: CourseIdPathParam,
|
|
117
|
-
call: ({ courseId }, { container,
|
|
90
|
+
call: ({ courseId }, { container, caller }) => container.reporting.admin.courseAnalytics(courseId, caller.actor)
|
|
91
|
+
}),
|
|
92
|
+
defineTool({
|
|
93
|
+
name: "list_course_learners",
|
|
94
|
+
title: "List course learners",
|
|
95
|
+
description: "Page through a course's learners: everyone with a grant that opens it, directly or through a bundle, active or not, with that grant and their progress. Search by name or email.",
|
|
96
|
+
kind: "read",
|
|
97
|
+
idempotent: true,
|
|
98
|
+
openWorld: false,
|
|
99
|
+
input: CourseLearnersQuery.extend(CourseIdPathParam.shape),
|
|
100
|
+
call: ({ courseId, ...query }, { container, caller }) => container.reporting.admin.courseLearners(courseId, query, caller.actor)
|
|
118
101
|
}),
|
|
119
102
|
defineTool({
|
|
120
103
|
name: "get_course_enrollment_trend",
|
|
121
104
|
title: "Get course enrollment trend",
|
|
122
105
|
description: "Daily enrollments in one course for the last days (default 30, at most 365).",
|
|
123
|
-
scope: "analytics:read",
|
|
124
106
|
kind: "read",
|
|
125
107
|
idempotent: true,
|
|
126
108
|
openWorld: false,
|
|
127
109
|
input: EnrollmentSeriesQuery.extend(CourseIdPathParam.shape),
|
|
128
|
-
call: ({ courseId, days }, { container,
|
|
110
|
+
call: ({ courseId, days }, { container, caller }) => container.reporting.admin.courseEnrollments(courseId, days, caller.actor)
|
|
129
111
|
}),
|
|
130
112
|
defineTool({
|
|
131
113
|
name: "get_student_analytics",
|
|
132
114
|
title: "Get student analytics",
|
|
133
115
|
description: "One student's enrolled, started and completed counts, average progress and progress in each course.",
|
|
134
|
-
scope: "analytics:read",
|
|
135
116
|
kind: "read",
|
|
136
117
|
idempotent: true,
|
|
137
118
|
openWorld: false,
|
|
138
119
|
input: StudentIdParam,
|
|
139
|
-
call: ({ id }, { container,
|
|
120
|
+
call: ({ id }, { container, caller }) => container.reporting.admin.studentAnalytics(id, caller.actor)
|
|
140
121
|
})
|
|
141
122
|
];
|
|
142
123
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"people.js","names":[],"sources":["../../../../src/http/mcp/tools/people.ts"],"sourcesContent":["import {\n CourseIdPathParam,\n
|
|
1
|
+
{"version":3,"file":"people.js","names":[],"sources":["../../../../src/http/mcp/tools/people.ts"],"sourcesContent":["import {\n CourseIdPathParam,\n CourseLearnersQuery,\n CreateInvite,\n EnrollmentSeriesQuery,\n EntitlementsQuery,\n GrantEntitlement,\n Invite,\n RevokeEntitlement,\n StudentIdParam,\n StudentsQuery,\n} from '../../schemas/index.js';\nimport { defineTool, noInput, type Tool } from '../tool.js';\n\nexport const peopleTools: Tool[] = [\n defineTool({\n name: 'list_students',\n title: 'List students',\n description:\n 'Search and page through the students with their access count, average progress, status and last activity.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: StudentsQuery,\n call: (input, { container, caller }) =>\n container.reporting.admin.listStudents(input, caller.actor),\n }),\n defineTool({\n name: 'invite_student',\n title: 'Invite student',\n description:\n 'Invite someone by email as a student. They exist as an invited student at once. sendEmail false skips the email.',\n kind: 'write',\n idempotent: false,\n openWorld: true,\n input: CreateInvite.omit({ roleKey: true }),\n output: Invite,\n call: (input, { container, caller }) =>\n container.organizations.createInvite(input, caller.actor, caller.userId),\n }),\n defineTool({\n name: 'list_access',\n title: 'List access',\n description: 'Who has access to what. Filter by student (orgUserId), content, type or status.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: EntitlementsQuery,\n call: (input, { container, caller }) => container.entitlements.list(input, caller.actor),\n }),\n defineTool({\n name: 'grant_access',\n title: 'Grant access',\n description:\n 'Give a student access to one course, download, community or event (contentId) or to a bundle (bundleId), never both. expiresAt is a date-time, or null for no expiry.',\n kind: 'write',\n idempotent: false,\n openWorld: false,\n input: GrantEntitlement,\n call: (input, { container, caller }) => container.entitlements.grant(input, caller.actor),\n }),\n defineTool({\n name: 'revoke_access',\n title: 'Revoke access',\n description:\n \"Revoke a student's direct grant to one course, download, community or event (contentId) or to a bundle (bundleId), never both. Access through a bundle stays. grant_access makes it active again.\",\n kind: 'destructive',\n idempotent: false,\n openWorld: false,\n input: RevokeEntitlement,\n call: (input, { container, caller }) => container.entitlements.revoke(input, caller.actor),\n }),\n defineTool({\n name: 'get_dashboard',\n title: 'Get dashboard',\n description:\n 'Published and draft course counts, active students, and access grants expiring soon.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: noInput,\n call: (_input, { container, caller }) => container.reporting.admin.overview(caller.actor),\n }),\n defineTool({\n name: 'get_enrollment_trend',\n title: 'Get enrollment trend',\n description:\n 'Daily enrollments across the organization for the last days (default 30, at most 365).',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: EnrollmentSeriesQuery,\n call: ({ days }, { container, caller }) =>\n container.reporting.admin.enrollments(days, caller.actor),\n }),\n defineTool({\n name: 'get_course_analytics',\n title: 'Get course analytics',\n description:\n 'Enrolled, started and completed counts, average progress and per-activity engagement for one course.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: CourseIdPathParam,\n call: ({ courseId }, { container, caller }) =>\n container.reporting.admin.courseAnalytics(courseId, caller.actor),\n }),\n defineTool({\n name: 'list_course_learners',\n title: 'List course learners',\n description:\n \"Page through a course's learners: everyone with a grant that opens it, directly or through a bundle, active or not, with that grant and their progress. Search by name or email.\",\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: CourseLearnersQuery.extend(CourseIdPathParam.shape),\n call: ({ courseId, ...query }, { container, caller }) =>\n container.reporting.admin.courseLearners(courseId, query, caller.actor),\n }),\n defineTool({\n name: 'get_course_enrollment_trend',\n title: 'Get course enrollment trend',\n description: 'Daily enrollments in one course for the last days (default 30, at most 365).',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: EnrollmentSeriesQuery.extend(CourseIdPathParam.shape),\n call: ({ courseId, days }, { container, caller }) =>\n container.reporting.admin.courseEnrollments(courseId, days, caller.actor),\n }),\n defineTool({\n name: 'get_student_analytics',\n title: 'Get student analytics',\n description:\n \"One student's enrolled, started and completed counts, average progress and progress in each course.\",\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: StudentIdParam,\n call: ({ id }, { container, caller }) =>\n container.reporting.admin.studentAnalytics(id, caller.actor),\n }),\n];\n"],"mappings":";;;;;;;;;AAcA,MAAa,cAAsB;CACjC,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,OAAO,EAAE,WAAW,aACzB,UAAU,UAAU,MAAM,aAAa,OAAO,OAAO,KAAK;CAC9D,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO,aAAa,KAAK,EAAE,SAAS,KAAK,CAAC;EAC1C,QAAQ;EACR,OAAO,OAAO,EAAE,WAAW,aACzB,UAAU,cAAc,aAAa,OAAO,OAAO,OAAO,OAAO,MAAM;CAC3E,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aAAa;EACb,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,OAAO,EAAE,WAAW,aAAa,UAAU,aAAa,KAAK,OAAO,OAAO,KAAK;CACzF,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,OAAO,EAAE,WAAW,aAAa,UAAU,aAAa,MAAM,OAAO,OAAO,KAAK;CAC1F,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,OAAO,EAAE,WAAW,aAAa,UAAU,aAAa,OAAO,OAAO,OAAO,KAAK;CAC3F,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,QAAQ,EAAE,WAAW,aAAa,UAAU,UAAU,MAAM,SAAS,OAAO,KAAK;CAC1F,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,EAAE,QAAQ,EAAE,WAAW,aAC5B,UAAU,UAAU,MAAM,YAAY,MAAM,OAAO,KAAK;CAC5D,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,EAAE,YAAY,EAAE,WAAW,aAChC,UAAU,UAAU,MAAM,gBAAgB,UAAU,OAAO,KAAK;CACpE,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO,oBAAoB,OAAO,kBAAkB,KAAK;EACzD,OAAO,EAAE,UAAU,GAAG,SAAS,EAAE,WAAW,aAC1C,UAAU,UAAU,MAAM,eAAe,UAAU,OAAO,OAAO,KAAK;CAC1E,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aAAa;EACb,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO,sBAAsB,OAAO,kBAAkB,KAAK;EAC3D,OAAO,EAAE,UAAU,QAAQ,EAAE,WAAW,aACtC,UAAU,UAAU,MAAM,kBAAkB,UAAU,MAAM,OAAO,KAAK;CAC5E,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,EAAE,MAAM,EAAE,WAAW,aAC1B,UAAU,UAAU,MAAM,iBAAiB,IAAI,OAAO,KAAK;CAC/D,CAAC;AACH"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { bridgeWebResponse, toWebRequest } from "../web-bridge.js";
|
|
2
|
+
import { isPermission } from "@headless-lms/core/organizations";
|
|
2
3
|
//#region src/http/plugins/auth.ts
|
|
3
4
|
/** The request carries no session. The central error handler maps this to 401. */
|
|
4
5
|
var UnauthorizedError = class extends Error {
|
|
@@ -7,6 +8,12 @@ var UnauthorizedError = class extends Error {
|
|
|
7
8
|
this.name = "UnauthorizedError";
|
|
8
9
|
}
|
|
9
10
|
};
|
|
11
|
+
function carriesKey(request) {
|
|
12
|
+
return request.headers["x-api-key"] !== void 0 || request.headers.authorization !== void 0;
|
|
13
|
+
}
|
|
14
|
+
function callerLimit(limit) {
|
|
15
|
+
return "roleKey" in limit ? limit : { scopes: limit.scopes.filter(isPermission) };
|
|
16
|
+
}
|
|
10
17
|
function registerAuth(app, appServer) {
|
|
11
18
|
const { auth } = appServer;
|
|
12
19
|
const origin = new URL(appServer.mcp.issuer).origin;
|
|
@@ -20,32 +27,54 @@ function registerAuth(app, appServer) {
|
|
|
20
27
|
await bridgeWebResponse(await auth.handler(toWebRequest(request, origin)), reply);
|
|
21
28
|
}
|
|
22
29
|
});
|
|
30
|
+
async function requireKey(request) {
|
|
31
|
+
const grant = await appServer.auth.verifyApiKey(request.headers);
|
|
32
|
+
if (!grant) throw new UnauthorizedError("The API key is not valid");
|
|
33
|
+
const caller = await appServer.organizations.resolveCaller(grant.orgId, grant.userId, callerLimit(grant.limit));
|
|
34
|
+
if (!caller) throw new UnauthorizedError("The API key's organization no longer exists");
|
|
35
|
+
request.authUserId = grant.userId;
|
|
36
|
+
request.userId = caller.userId ?? grant.userId;
|
|
37
|
+
request.orgId = caller.orgId;
|
|
38
|
+
request.authOrgId = grant.orgId;
|
|
39
|
+
request.resolvedCaller = caller;
|
|
40
|
+
appServer.requestContext.set({ orgId: caller.orgId });
|
|
41
|
+
}
|
|
23
42
|
app.decorate("requireSession", async (request) => {
|
|
43
|
+
if (carriesKey(request)) throw new UnauthorizedError("Sign in to continue; an API key is not accepted here");
|
|
24
44
|
const session = await appServer.auth.verify(request.headers);
|
|
25
45
|
if (!session) throw new UnauthorizedError();
|
|
26
46
|
request.authUser = session.user;
|
|
47
|
+
request.authUserId = session.user.id;
|
|
27
48
|
request.userId = session.user.id;
|
|
28
49
|
const authOrgId = session.session.activeOrganizationId;
|
|
29
50
|
if (authOrgId) {
|
|
30
51
|
request.authOrgId = authOrgId;
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
33
|
-
request.
|
|
34
|
-
|
|
52
|
+
const caller = await appServer.organizations.resolveCaller(authOrgId, session.user.id);
|
|
53
|
+
if (caller) {
|
|
54
|
+
request.resolvedCaller = caller;
|
|
55
|
+
request.orgId = caller.orgId;
|
|
56
|
+
appServer.requestContext.set({ orgId: caller.orgId });
|
|
35
57
|
}
|
|
36
58
|
}
|
|
37
59
|
});
|
|
38
60
|
app.decorate("requireOrgSession", async (request) => {
|
|
61
|
+
if (request.resolvedCaller && request.orgId) return;
|
|
62
|
+
if (carriesKey(request)) {
|
|
63
|
+
await requireKey(request);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
39
66
|
const session = await appServer.auth.verify(request.headers);
|
|
40
67
|
if (!session) throw new UnauthorizedError();
|
|
41
68
|
const authOrgId = session.session.activeOrganizationId;
|
|
42
69
|
if (!authOrgId) throw new UnauthorizedError("Choose an organization to continue");
|
|
43
|
-
const
|
|
44
|
-
if (!
|
|
70
|
+
const caller = await appServer.organizations.resolveCaller(authOrgId, session.user.id);
|
|
71
|
+
if (!caller) throw new UnauthorizedError("Your session points to an organization that no longer exists");
|
|
45
72
|
request.authUser = session.user;
|
|
73
|
+
request.authUserId = session.user.id;
|
|
46
74
|
request.userId = session.user.id;
|
|
47
|
-
request.orgId =
|
|
48
|
-
|
|
75
|
+
request.orgId = caller.orgId;
|
|
76
|
+
request.resolvedCaller = caller;
|
|
77
|
+
appServer.requestContext.set({ orgId: caller.orgId });
|
|
49
78
|
request.authOrgId = authOrgId;
|
|
50
79
|
});
|
|
51
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","names":[],"sources":["../../../src/http/plugins/auth.ts"],"sourcesContent":["// Everything better-auth touches on the HTTP surface:\n// - the /api/auth/* catch-all bridged to better-auth's Web handler\n// - the `requireSession` / `requireOrgSession` decorators routes guard with\nimport type { FastifyInstance, FastifyRequest } from 'fastify';\nimport type { Container } from '../../app/container.js';\nimport { bridgeWebResponse, toWebRequest } from '../web-bridge.js';\n\n/** The request carries no session. The central error handler maps this to 401. */\nexport class UnauthorizedError extends Error {\n constructor(message = 'Sign in to continue') {\n super(message);\n this.name = 'UnauthorizedError';\n }\n}\n\nexport function registerAuth(app: FastifyInstance, appServer: Container): void {\n const { auth } = appServer;\n const origin = new URL(appServer.mcp.issuer).origin;\n\n app.addContentTypeParser(\n 'application/x-www-form-urlencoded',\n { parseAs: 'string' },\n (_request, body, done) => {\n done(null, body);\n },\n );\n\n // Mount better-auth: a catch-all that bridges Fastify <-> Web Request/Response.\n app.route({\n method: ['GET', 'POST'],\n url: '/api/auth/*',\n async handler(request, reply) {\n await bridgeWebResponse(await auth.handler(toWebRequest(request, origin)), reply);\n },\n });\n\n // Session only, no active org required. For the routes a caller reaches\n // *before* they have an org: creating their first organization, and accepting\n // an invitation (which is what stamps the active org onto the session).\n app.decorate('requireSession', async (request: FastifyRequest) => {\n const session = await appServer.auth.verify(request.headers);\n if (!session) {\n throw new UnauthorizedError();\n }\n request.authUser = session.user;\n request.userId = session.user.id;\n\n const authOrgId = session.session.activeOrganizationId;\n if (authOrgId) {\n request.authOrgId = authOrgId;\n const
|
|
1
|
+
{"version":3,"file":"auth.js","names":[],"sources":["../../../src/http/plugins/auth.ts"],"sourcesContent":["// Everything better-auth touches on the HTTP surface:\n// - the /api/auth/* catch-all bridged to better-auth's Web handler\n// - the `requireSession` / `requireOrgSession` decorators routes guard with\nimport type { FastifyInstance, FastifyRequest } from 'fastify';\nimport type { Container } from '../../app/container.js';\nimport { isPermission } from '@headless-lms/core/organizations';\nimport type { CallerLimit } from '@headless-lms/core/organizations';\nimport type { CredentialLimit } from '@headless-lms/core/types';\nimport { bridgeWebResponse, toWebRequest } from '../web-bridge.js';\n\n/** The request carries no session. The central error handler maps this to 401. */\nexport class UnauthorizedError extends Error {\n constructor(message = 'Sign in to continue') {\n super(message);\n this.name = 'UnauthorizedError';\n }\n}\n\nfunction carriesKey(request: FastifyRequest): boolean {\n return request.headers['x-api-key'] !== undefined || request.headers.authorization !== undefined;\n}\n\nfunction callerLimit(limit: CredentialLimit): CallerLimit {\n return 'roleKey' in limit ? limit : { scopes: limit.scopes.filter(isPermission) };\n}\n\nexport function registerAuth(app: FastifyInstance, appServer: Container): void {\n const { auth } = appServer;\n const origin = new URL(appServer.mcp.issuer).origin;\n\n app.addContentTypeParser(\n 'application/x-www-form-urlencoded',\n { parseAs: 'string' },\n (_request, body, done) => {\n done(null, body);\n },\n );\n\n // Mount better-auth: a catch-all that bridges Fastify <-> Web Request/Response.\n app.route({\n method: ['GET', 'POST'],\n url: '/api/auth/*',\n async handler(request, reply) {\n await bridgeWebResponse(await auth.handler(toWebRequest(request, origin)), reply);\n },\n });\n\n async function requireKey(request: FastifyRequest): Promise<void> {\n const grant = await appServer.auth.verifyApiKey(request.headers);\n if (!grant) {\n throw new UnauthorizedError('The API key is not valid');\n }\n const caller = await appServer.organizations.resolveCaller(\n grant.orgId,\n grant.userId,\n callerLimit(grant.limit),\n );\n if (!caller) {\n throw new UnauthorizedError(\"The API key's organization no longer exists\");\n }\n request.authUserId = grant.userId;\n request.userId = caller.userId ?? grant.userId;\n request.orgId = caller.orgId;\n request.authOrgId = grant.orgId;\n request.resolvedCaller = caller;\n appServer.requestContext.set({ orgId: caller.orgId });\n }\n\n // Session only, no active org required. For the routes a caller reaches\n // *before* they have an org: creating their first organization, and accepting\n // an invitation (which is what stamps the active org onto the session).\n app.decorate('requireSession', async (request: FastifyRequest) => {\n if (carriesKey(request)) {\n throw new UnauthorizedError('Sign in to continue; an API key is not accepted here');\n }\n const session = await appServer.auth.verify(request.headers);\n if (!session) {\n throw new UnauthorizedError();\n }\n request.authUser = session.user;\n request.authUserId = session.user.id;\n request.userId = session.user.id;\n\n const authOrgId = session.session.activeOrganizationId;\n if (authOrgId) {\n request.authOrgId = authOrgId;\n const caller = await appServer.organizations.resolveCaller(authOrgId, session.user.id);\n if (caller) {\n request.resolvedCaller = caller;\n request.orgId = caller.orgId;\n appServer.requestContext.set({ orgId: caller.orgId });\n }\n }\n });\n\n // Requires a valid API key, or a valid session with an active org set — every org-scoped route.\n app.decorate('requireOrgSession', async (request: FastifyRequest) => {\n if (request.resolvedCaller && request.orgId) {\n return;\n }\n if (carriesKey(request)) {\n await requireKey(request);\n return;\n }\n const session = await appServer.auth.verify(request.headers);\n if (!session) {\n throw new UnauthorizedError();\n }\n const authOrgId = session.session.activeOrganizationId;\n if (!authOrgId) {\n throw new UnauthorizedError('Choose an organization to continue');\n }\n\n const caller = await appServer.organizations.resolveCaller(authOrgId, session.user.id);\n if (!caller) {\n throw new UnauthorizedError('Your session points to an organization that no longer exists');\n }\n request.authUser = session.user;\n request.authUserId = session.user.id;\n request.userId = session.user.id;\n request.orgId = caller.orgId;\n request.resolvedCaller = caller;\n appServer.requestContext.set({ orgId: caller.orgId });\n // resolveScope requires the auth-provider id too — routes that write back\n // through better-auth key on it.\n request.authOrgId = authOrgId;\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { FastifyReply, FastifyRequest } from "fastify";
|
|
2
2
|
//#region src/http/plugins/error-handler.d.ts
|
|
3
|
+
declare function refusalSubject(error: unknown): {
|
|
4
|
+
permission?: string;
|
|
5
|
+
contentId?: string;
|
|
6
|
+
};
|
|
3
7
|
declare function errorHandler(error: unknown, request: FastifyRequest, reply: FastifyReply): FastifyReply<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
|
|
4
8
|
declare function notFoundHandler(request: FastifyRequest, reply: FastifyReply): FastifyReply<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
|
|
5
9
|
//#endregion
|
|
6
|
-
export { errorHandler, notFoundHandler };
|
|
10
|
+
export { errorHandler, notFoundHandler, refusalSubject };
|
|
7
11
|
//# sourceMappingURL=error-handler.d.ts.map
|
|
@@ -5,6 +5,13 @@ import { isResponseSerializationError } from "fastify-type-provider-zod";
|
|
|
5
5
|
function routeOf(request) {
|
|
6
6
|
return `${request.method} ${request.routeOptions.url ?? "unmatched route"}`;
|
|
7
7
|
}
|
|
8
|
+
function refusalSubject(error) {
|
|
9
|
+
const subject = error;
|
|
10
|
+
return {
|
|
11
|
+
...typeof subject?.permission === "string" && { permission: subject.permission },
|
|
12
|
+
...typeof subject?.contentId === "string" && { contentId: subject.contentId }
|
|
13
|
+
};
|
|
14
|
+
}
|
|
8
15
|
function errorHandler(error, request, reply) {
|
|
9
16
|
if (error instanceof ActionInvocationError) {
|
|
10
17
|
request.log.error({
|
|
@@ -24,12 +31,15 @@ function errorHandler(error, request, reply) {
|
|
|
24
31
|
error: refusal.error,
|
|
25
32
|
message: refusal.message,
|
|
26
33
|
...refusal.issues && { issues: refusal.issues },
|
|
27
|
-
...refusal.
|
|
34
|
+
...refusal.details && { details: refusal.details },
|
|
35
|
+
...refusal.error === "bad_request" && { cause: error.message },
|
|
36
|
+
...refusalSubject(error)
|
|
28
37
|
}, `${routeOf(request)} refused`);
|
|
29
38
|
return reply.status(refusal.status).send({
|
|
30
39
|
error: refusal.error,
|
|
31
40
|
message: refusal.message,
|
|
32
|
-
...refusal.issues && { issues: refusal.issues }
|
|
41
|
+
...refusal.issues && { issues: refusal.issues },
|
|
42
|
+
...refusal.details && { details: refusal.details }
|
|
33
43
|
});
|
|
34
44
|
}
|
|
35
45
|
if (isResponseSerializationError(error)) request.log.error({
|
|
@@ -54,6 +64,6 @@ function notFoundHandler(request, reply) {
|
|
|
54
64
|
});
|
|
55
65
|
}
|
|
56
66
|
//#endregion
|
|
57
|
-
export { errorHandler, notFoundHandler };
|
|
67
|
+
export { errorHandler, notFoundHandler, refusalSubject };
|
|
58
68
|
|
|
59
69
|
//# sourceMappingURL=error-handler.js.map
|