@headless-lms/server 0.4.0 → 0.6.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.
Files changed (222) hide show
  1. package/README.md +14 -0
  2. package/dist/app/community-mail.d.ts +5 -3
  3. package/dist/app/community-mail.js +60 -12
  4. package/dist/app/community-mail.js.map +1 -1
  5. package/dist/app/container.d.ts +24 -3
  6. package/dist/app/container.js +102 -38
  7. package/dist/app/container.js.map +1 -1
  8. package/dist/app/notification-channels.js +1 -0
  9. package/dist/app/notification-channels.js.map +1 -1
  10. package/dist/app/notification-reads.d.ts +3 -3
  11. package/dist/app/notification-reads.js +7 -28
  12. package/dist/app/notification-reads.js.map +1 -1
  13. package/dist/app/platform.d.ts +14 -0
  14. package/dist/app/platform.js +14 -0
  15. package/dist/app/platform.js.map +1 -0
  16. package/dist/http/config.d.ts +4 -0
  17. package/dist/http/errors.js +9 -4
  18. package/dist/http/errors.js.map +1 -1
  19. package/dist/http/fastify.d.ts +8 -0
  20. package/dist/http/mcp/catalogue.d.ts +11 -9
  21. package/dist/http/mcp/catalogue.js +15 -21
  22. package/dist/http/mcp/catalogue.js.map +1 -1
  23. package/dist/http/mcp/challenge.js +3 -3
  24. package/dist/http/mcp/challenge.js.map +1 -1
  25. package/dist/http/mcp/route.js +7 -7
  26. package/dist/http/mcp/route.js.map +1 -1
  27. package/dist/http/mcp/test-support.d.ts +6 -5
  28. package/dist/http/mcp/test-support.js +32 -11
  29. package/dist/http/mcp/test-support.js.map +1 -1
  30. package/dist/http/mcp/tool.d.ts +3 -9
  31. package/dist/http/mcp/tool.js +14 -6
  32. package/dist/http/mcp/tool.js.map +1 -1
  33. package/dist/http/mcp/tools/automations.js +10 -20
  34. package/dist/http/mcp/tools/automations.js.map +1 -1
  35. package/dist/http/mcp/tools/catalog.js +25 -42
  36. package/dist/http/mcp/tools/catalog.js.map +1 -1
  37. package/dist/http/mcp/tools/comments.js +9 -19
  38. package/dist/http/mcp/tools/comments.js.map +1 -1
  39. package/dist/http/mcp/tools/communities.js +17 -32
  40. package/dist/http/mcp/tools/communities.js.map +1 -1
  41. package/dist/http/mcp/tools/community-members.js +9 -16
  42. package/dist/http/mcp/tools/community-members.js.map +1 -1
  43. package/dist/http/mcp/tools/community-moderation.js +13 -25
  44. package/dist/http/mcp/tools/community-moderation.js.map +1 -1
  45. package/dist/http/mcp/tools/courses.js +11 -21
  46. package/dist/http/mcp/tools/courses.js.map +1 -1
  47. package/dist/http/mcp/tools/events.d.ts +6 -0
  48. package/dist/http/mcp/tools/events.js +170 -0
  49. package/dist/http/mcp/tools/events.js.map +1 -0
  50. package/dist/http/mcp/tools/index.d.ts +5 -1
  51. package/dist/http/mcp/tools/index.js +45 -10
  52. package/dist/http/mcp/tools/index.js.map +1 -1
  53. package/dist/http/mcp/tools/organization.d.ts +6 -0
  54. package/dist/http/mcp/tools/organization.js +214 -0
  55. package/dist/http/mcp/tools/organization.js.map +1 -0
  56. package/dist/http/mcp/tools/people.js +30 -49
  57. package/dist/http/mcp/tools/people.js.map +1 -1
  58. package/dist/http/plugins/auth.js +37 -8
  59. package/dist/http/plugins/auth.js.map +1 -1
  60. package/dist/http/plugins/error-handler.d.ts +5 -1
  61. package/dist/http/plugins/error-handler.js +10 -2
  62. package/dist/http/plugins/error-handler.js.map +1 -1
  63. package/dist/http/plugins/openapi.js +18 -2
  64. package/dist/http/plugins/openapi.js.map +1 -1
  65. package/dist/http/plugins/platform.d.ts +7 -0
  66. package/dist/http/plugins/platform.js +13 -0
  67. package/dist/http/plugins/platform.js.map +1 -0
  68. package/dist/http/routes/access.js +54 -36
  69. package/dist/http/routes/access.js.map +1 -1
  70. package/dist/http/routes/activities.js +45 -45
  71. package/dist/http/routes/activities.js.map +1 -1
  72. package/dist/http/routes/assets.js +30 -65
  73. package/dist/http/routes/assets.js.map +1 -1
  74. package/dist/http/routes/automations.js +37 -42
  75. package/dist/http/routes/automations.js.map +1 -1
  76. package/dist/http/routes/bundles.js +33 -36
  77. package/dist/http/routes/bundles.js.map +1 -1
  78. package/dist/http/routes/communities.js +93 -145
  79. package/dist/http/routes/communities.js.map +1 -1
  80. package/dist/http/routes/community-moderation.js +83 -137
  81. package/dist/http/routes/community-moderation.js.map +1 -1
  82. package/dist/http/routes/course-analytics.js +32 -15
  83. package/dist/http/routes/course-analytics.js.map +1 -1
  84. package/dist/http/routes/courses.js +25 -32
  85. package/dist/http/routes/courses.js.map +1 -1
  86. package/dist/http/routes/dashboard.js +9 -9
  87. package/dist/http/routes/dashboard.js.map +1 -1
  88. package/dist/http/routes/discussion.js +21 -39
  89. package/dist/http/routes/discussion.js.map +1 -1
  90. package/dist/http/routes/downloads.js +41 -47
  91. package/dist/http/routes/downloads.js.map +1 -1
  92. package/dist/http/routes/entitlements.js +19 -23
  93. package/dist/http/routes/entitlements.js.map +1 -1
  94. package/dist/http/routes/events.d.ts +7 -0
  95. package/dist/http/routes/events.js +333 -0
  96. package/dist/http/routes/events.js.map +1 -0
  97. package/dist/http/routes/integrations.js +40 -73
  98. package/dist/http/routes/integrations.js.map +1 -1
  99. package/dist/http/routes/member/assets.js +10 -13
  100. package/dist/http/routes/member/assets.js.map +1 -1
  101. package/dist/http/routes/member/chats.js +170 -47
  102. package/dist/http/routes/member/chats.js.map +1 -1
  103. package/dist/http/routes/member/comments.js +65 -26
  104. package/dist/http/routes/member/comments.js.map +1 -1
  105. package/dist/http/routes/member/community.js +45 -52
  106. package/dist/http/routes/member/community.js.map +1 -1
  107. package/dist/http/routes/member/courses.js +12 -19
  108. package/dist/http/routes/member/courses.js.map +1 -1
  109. package/dist/http/routes/member/devices.js +9 -10
  110. package/dist/http/routes/member/devices.js.map +1 -1
  111. package/dist/http/routes/member/downloads.js +6 -9
  112. package/dist/http/routes/member/downloads.js.map +1 -1
  113. package/dist/http/routes/member/events.d.ts +7 -0
  114. package/dist/http/routes/member/events.js +386 -0
  115. package/dist/http/routes/member/events.js.map +1 -0
  116. package/dist/http/routes/member/files.js +88 -46
  117. package/dist/http/routes/member/files.js.map +1 -1
  118. package/dist/http/routes/member/index.js +2 -0
  119. package/dist/http/routes/member/index.js.map +1 -1
  120. package/dist/http/routes/member/notification-settings.js +15 -23
  121. package/dist/http/routes/member/notification-settings.js.map +1 -1
  122. package/dist/http/routes/member/notifications.js +16 -16
  123. package/dist/http/routes/member/notifications.js.map +1 -1
  124. package/dist/http/routes/member/org.js +5 -15
  125. package/dist/http/routes/member/org.js.map +1 -1
  126. package/dist/http/routes/member/posts.js +67 -68
  127. package/dist/http/routes/member/posts.js.map +1 -1
  128. package/dist/http/routes/member/profile.js +15 -6
  129. package/dist/http/routes/member/profile.js.map +1 -1
  130. package/dist/http/routes/member/progress.js +7 -11
  131. package/dist/http/routes/member/progress.js.map +1 -1
  132. package/dist/http/routes/organizations.js +319 -135
  133. package/dist/http/routes/organizations.js.map +1 -1
  134. package/dist/http/routes/platform.d.ts +7 -0
  135. package/dist/http/routes/platform.js +61 -0
  136. package/dist/http/routes/platform.js.map +1 -0
  137. package/dist/http/routes/students.js +12 -75
  138. package/dist/http/routes/students.js.map +1 -1
  139. package/dist/http/routes/webhooks.js +30 -39
  140. package/dist/http/routes/webhooks.js.map +1 -1
  141. package/dist/http/routes.js +9 -0
  142. package/dist/http/routes.js.map +1 -1
  143. package/dist/http/schemas/access.d.ts +346 -64
  144. package/dist/http/schemas/access.js +39 -12
  145. package/dist/http/schemas/access.js.map +1 -1
  146. package/dist/http/schemas/assets.d.ts +8 -3
  147. package/dist/http/schemas/assets.js +6 -5
  148. package/dist/http/schemas/assets.js.map +1 -1
  149. package/dist/http/schemas/automations.d.ts +4 -4
  150. package/dist/http/schemas/chats.d.ts +216 -37
  151. package/dist/http/schemas/chats.js +13 -4
  152. package/dist/http/schemas/chats.js.map +1 -1
  153. package/dist/http/schemas/community.d.ts +105 -31
  154. package/dist/http/schemas/community.js +25 -9
  155. package/dist/http/schemas/community.js.map +1 -1
  156. package/dist/http/schemas/course-analytics.d.ts +51 -1
  157. package/dist/http/schemas/course-analytics.js +19 -1
  158. package/dist/http/schemas/course-analytics.js.map +1 -1
  159. package/dist/http/schemas/dashboard.d.ts +1 -0
  160. package/dist/http/schemas/dashboard.js +2 -1
  161. package/dist/http/schemas/dashboard.js.map +1 -1
  162. package/dist/http/schemas/discussion.d.ts +22 -72
  163. package/dist/http/schemas/discussion.js +2 -2
  164. package/dist/http/schemas/discussion.js.map +1 -1
  165. package/dist/http/schemas/entitlements.d.ts +9 -12
  166. package/dist/http/schemas/entitlements.js +11 -5
  167. package/dist/http/schemas/entitlements.js.map +1 -1
  168. package/dist/http/schemas/events.d.ts +527 -0
  169. package/dist/http/schemas/events.js +64 -0
  170. package/dist/http/schemas/events.js.map +1 -0
  171. package/dist/http/schemas/files.d.ts +24 -5
  172. package/dist/http/schemas/files.js +15 -2
  173. package/dist/http/schemas/files.js.map +1 -1
  174. package/dist/http/schemas/index.d.ts +11 -9
  175. package/dist/http/schemas/index.js +11 -9
  176. package/dist/http/schemas/invites.d.ts +3 -47
  177. package/dist/http/schemas/invites.js +4 -25
  178. package/dist/http/schemas/invites.js.map +1 -1
  179. package/dist/http/schemas/member.d.ts +94 -7
  180. package/dist/http/schemas/member.js +7 -3
  181. package/dist/http/schemas/member.js.map +1 -1
  182. package/dist/http/schemas/members.d.ts +329 -65
  183. package/dist/http/schemas/members.js +67 -25
  184. package/dist/http/schemas/members.js.map +1 -1
  185. package/dist/http/schemas/notifications.d.ts +58 -54
  186. package/dist/http/schemas/platform.d.ts +38 -0
  187. package/dist/http/schemas/platform.js +19 -0
  188. package/dist/http/schemas/platform.js.map +1 -0
  189. package/dist/http/schemas/posts.d.ts +55 -35
  190. package/dist/http/schemas/posts.js +4 -4
  191. package/dist/http/schemas/posts.js.map +1 -1
  192. package/dist/http/schemas/profile.d.ts +1 -1
  193. package/dist/http/schemas/shared.d.ts +1 -11
  194. package/dist/http/schemas/shared.js +1 -10
  195. package/dist/http/schemas/shared.js.map +1 -1
  196. package/dist/http/schemas/students.d.ts +22 -0
  197. package/dist/http/schemas/students.js +3 -1
  198. package/dist/http/schemas/students.js.map +1 -1
  199. package/dist/http/scope.d.ts +9 -13
  200. package/dist/http/scope.js +29 -40
  201. package/dist/http/scope.js.map +1 -1
  202. package/dist/http/server.js +9 -0
  203. package/dist/http/server.js.map +1 -1
  204. package/dist/http/shutdown.d.ts +11 -0
  205. package/dist/http/shutdown.js +40 -0
  206. package/dist/http/shutdown.js.map +1 -0
  207. package/dist/http/sse.d.ts +19 -0
  208. package/dist/http/sse.js +104 -0
  209. package/dist/http/sse.js.map +1 -0
  210. package/dist/http/test-caller.d.ts +13 -0
  211. package/dist/http/test-caller.js +14 -0
  212. package/dist/http/test-caller.js.map +1 -0
  213. package/dist/http/well-known.js +2 -2
  214. package/dist/http/well-known.js.map +1 -1
  215. package/dist/index.d.ts +4 -2
  216. package/dist/index.js +4 -2
  217. package/dist/index.js.map +1 -1
  218. package/dist/packages/server/package.js +1 -1
  219. package/package.json +7 -6
  220. package/dist/http/routes/member/actor.d.ts +0 -14
  221. package/dist/http/routes/member/actor.js +0 -11
  222. package/dist/http/routes/member/actor.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { Container } from "../../app/container.js";
2
2
  import { Tool, ToolContext, ToolKind } from "./tool.js";
3
- import { AccessScope, StaffRole } from "@headless-lms/core/organizations";
3
+ import { Actor } from "@headless-lms/core/shared/actor";
4
4
  //#region src/http/mcp/test-support.d.ts
5
5
  declare const ORG: {
6
6
  id: string;
@@ -19,10 +19,11 @@ declare const ORG_USER: {
19
19
  id: string;
20
20
  orgId: string;
21
21
  userId: string;
22
- role: string;
22
+ status: string;
23
23
  };
24
+ declare const OWNER: Actor;
24
25
  declare function fakeContainer(overrides?: Record<string, unknown>): Container;
25
- declare function toolContext(container: Container, role?: StaffRole): ToolContext;
26
+ declare function toolContext(container: Container, actor?: Actor): ToolContext;
26
27
  declare function listTools(ctx: ToolContext, tools?: readonly Tool[]): Promise<{
27
28
  name: string;
28
29
  annotations?: Record<string, unknown>;
@@ -39,7 +40,7 @@ declare function callTool(name: string, args: unknown, ctx: ToolContext, tools?:
39
40
  text?: string;
40
41
  }>;
41
42
  }>;
42
- type ToolSpec = [name: string, scope: AccessScope, kind: ToolKind, idempotent: boolean, openWorld: boolean];
43
+ type ToolSpec = [name: string, kind: ToolKind, idempotent: boolean, openWorld: boolean];
43
44
  declare function toolSpecs(tools: readonly Tool[]): ToolSpec[];
44
45
  type ToolCase = {
45
46
  tool: string;
@@ -51,5 +52,5 @@ type ToolCase = {
51
52
  };
52
53
  declare function runCase(tools: readonly Tool[], c: ToolCase): Promise<void>;
53
54
  //#endregion
54
- export { ORG, ORG_USER, ToolCase, ToolSpec, USER, callTool, fakeContainer, listTools, runCase, toolContext, toolSpecs };
55
+ export { ORG, ORG_USER, OWNER, ToolCase, ToolSpec, USER, callTool, fakeContainer, listTools, runCase, toolContext, toolSpecs };
55
56
  //# sourceMappingURL=test-support.d.ts.map
@@ -3,7 +3,7 @@ import { toToolResult } from "./tool.js";
3
3
  import { createToolServer } from "./server.js";
4
4
  import { TOOLS } from "./tools/index.js";
5
5
  import { a as globalExpect } from "../../node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/vi.DgezovHB.js";
6
- import { ACCESS_SCOPES } from "@headless-lms/core/organizations";
6
+ import { ALL_PERMISSIONS, actorWith } from "@headless-lms/core/organizations";
7
7
  import { createMcpHandler } from "@modelcontextprotocol/server";
8
8
  //#region src/http/mcp/test-support.ts
9
9
  const ORG = {
@@ -23,8 +23,13 @@ const ORG_USER = {
23
23
  id: "ou_1",
24
24
  orgId: "org_1",
25
25
  userId: "usr_1",
26
- role: "owner"
26
+ status: "active"
27
27
  };
28
+ const OWNER = actorWith({
29
+ id: ORG_USER.id,
30
+ orgId: ORG.id,
31
+ owner: true
32
+ });
28
33
  function fakeContainer(overrides = {}) {
29
34
  const logger = {
30
35
  debug: () => {},
@@ -46,13 +51,29 @@ function fakeContainer(overrides = {}) {
46
51
  auth: { verifyAccess: async () => ({
47
52
  userId: USER.id,
48
53
  orgId: ORG.id,
49
- scopes: [...ACCESS_SCOPES],
50
- clientId: "client_1"
54
+ clientId: "client_1",
55
+ limit: { scopes: [...ALL_PERMISSIONS] }
51
56
  }) },
52
57
  organizations: {
53
58
  getById: async (id) => id === ORG.id ? ORG : null,
54
59
  getByExternalId: async (id) => id === ORG.externalId ? ORG : null,
55
- getOrgUser: async (orgId, userId) => orgId === ORG.id && userId === USER.id ? ORG_USER : null
60
+ getOrgUser: async (orgId, userId) => orgId === ORG.id && userId === USER.id ? ORG_USER : null,
61
+ actorFor: async (orgId, orgUserId) => orgId === ORG.id && orgUserId === ORG_USER.id ? OWNER : null,
62
+ resolveCaller: async (authOrgId, authUserId, limit) => {
63
+ const orgs = base.organizations;
64
+ const people = base.identity;
65
+ const org = await orgs.getByExternalId(authOrgId);
66
+ if (!org) return null;
67
+ const user = await people.getUserByExternalId(authUserId);
68
+ const orgUser = user ? await orgs.getOrgUser(org.id, user.id) : null;
69
+ const actor = orgUser ? await orgs.actorFor(org.id, orgUser.id, limit) : null;
70
+ return {
71
+ orgId: org.id,
72
+ userId: user?.id ?? null,
73
+ orgUserId: orgUser?.id ?? null,
74
+ actor
75
+ };
76
+ }
56
77
  },
57
78
  identity: {
58
79
  getUserByExternalId: async (id) => id === USER.id ? USER : null,
@@ -68,15 +89,16 @@ function fakeContainer(overrides = {}) {
68
89
  }
69
90
  return base;
70
91
  }
71
- function toolContext(container, role = "owner") {
92
+ function toolContext(container, actor = OWNER) {
72
93
  return {
73
94
  container,
74
- scope: {
95
+ caller: {
96
+ actor,
75
97
  orgId: ORG.id,
76
98
  userId: USER.id,
77
99
  orgUserId: ORG_USER.id,
78
- role,
79
- authOrgId: ORG.id
100
+ authOrgId: ORG.id,
101
+ authUserId: USER.id
80
102
  },
81
103
  requestId: "req_test"
82
104
  };
@@ -121,7 +143,6 @@ async function callTool(name, args, ctx, tools = TOOLS) {
121
143
  function toolSpecs(tools) {
122
144
  return tools.map((tool) => [
123
145
  tool.name,
124
- tool.scope,
125
146
  tool.kind,
126
147
  tool.idempotent,
127
148
  tool.openWorld
@@ -146,6 +167,6 @@ async function runCase(tools, c) {
146
167
  globalExpect(result.structuredContent).toEqual(toToolResult("shown" in c ? c.shown : returns).structuredContent);
147
168
  }
148
169
  //#endregion
149
- export { ORG, ORG_USER, USER, callTool, fakeContainer, listTools, runCase, toolContext, toolSpecs };
170
+ export { ORG, ORG_USER, OWNER, USER, callTool, fakeContainer, listTools, runCase, toolContext, toolSpecs };
150
171
 
151
172
  //# sourceMappingURL=test-support.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-support.js","names":[],"sources":["../../../src/http/mcp/test-support.ts"],"sourcesContent":["import { expect } from 'vitest';\nimport { createMcpHandler } from '@modelcontextprotocol/server';\nimport { ACCESS_SCOPES, type AccessScope, type StaffRole } from '@headless-lms/core/organizations';\nimport type { Container } from '../../app/container.js';\nimport { mcpEndpoints } from '../../app/mcp-endpoints.js';\nimport { createToolServer } from './server.js';\nimport { toToolResult, type Tool, type ToolContext, type ToolKind } from './tool.js';\nimport { TOOLS } from './tools/index.js';\n\nexport const ORG = { id: 'org_1', name: 'Acme', slug: 'acme', externalId: 'org_1' };\nexport const USER = {\n id: 'usr_1',\n email: 'owner@acme.test',\n firstName: 'Ada',\n lastName: 'Lovelace',\n externalId: 'usr_1',\n};\nexport const ORG_USER = { id: 'ou_1', orgId: 'org_1', userId: 'usr_1', role: 'owner' };\n\nexport function fakeContainer(overrides: Record<string, unknown> = {}): Container {\n const logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n child: () => logger,\n };\n const base: Record<string, unknown> = {\n mcp: mcpEndpoints({\n authBaseURL: 'http://localhost:8100',\n adminAppUrl: 'http://localhost:8101',\n }),\n logger,\n requestContext: { set: () => {}, run: (_ctx: unknown, fn: () => void) => fn() },\n auth: {\n verifyAccess: async () => ({\n userId: USER.id,\n orgId: ORG.id,\n scopes: [...ACCESS_SCOPES],\n clientId: 'client_1',\n }),\n },\n organizations: {\n getById: async (id: string) => (id === ORG.id ? ORG : null),\n getByExternalId: async (id: string) => (id === ORG.externalId ? ORG : null),\n getOrgUser: async (orgId: string, userId: string) =>\n orgId === ORG.id && userId === USER.id ? ORG_USER : null,\n },\n identity: {\n getUserByExternalId: async (id: string) => (id === USER.id ? USER : null),\n getUserById: async (id: string) => (id === USER.id ? USER : null),\n },\n };\n for (const [key, value] of Object.entries(overrides)) {\n const current = base[key];\n base[key] =\n current &&\n typeof current === 'object' &&\n value &&\n typeof value === 'object' &&\n !Array.isArray(value)\n ? { ...(current as object), ...(value as object) }\n : value;\n }\n return base as unknown as Container;\n}\n\nexport function toolContext(container: Container, role: StaffRole = 'owner'): ToolContext {\n return {\n container,\n scope: { orgId: ORG.id, userId: USER.id, orgUserId: ORG_USER.id, role, authOrgId: ORG.id },\n requestId: 'req_test',\n };\n}\n\nconst MODERN_META = {\n 'io.modelcontextprotocol/protocolVersion': '2026-07-28',\n 'io.modelcontextprotocol/clientCapabilities': {},\n};\n\nasync function rpc(\n ctx: ToolContext,\n tools: readonly Tool[],\n method: string,\n params: Record<string, unknown>,\n name?: string,\n) {\n const handler = createMcpHandler(() => createToolServer(ctx, tools));\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n 'mcp-protocol-version': '2026-07-28',\n 'mcp-method': method,\n };\n if (name) {\n headers['mcp-name'] = name;\n }\n const response = await handler.fetch(\n new Request('http://localhost:8100/mcp', {\n method: 'POST',\n headers,\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method,\n params: { ...params, _meta: MODERN_META },\n }),\n }),\n );\n const body = (await response.json()) as {\n result?: Record<string, unknown>;\n error?: { message: string };\n };\n if (body.error) {\n throw new Error(body.error.message);\n }\n return body.result as Record<string, unknown>;\n}\n\nexport async function listTools(ctx: ToolContext, tools: readonly Tool[] = TOOLS) {\n const result = await rpc(ctx, tools, 'tools/list', {});\n return result.tools as Array<{\n name: string;\n annotations?: Record<string, unknown>;\n inputSchema: { type?: string; properties?: Record<string, unknown> };\n }>;\n}\n\nexport async function callTool(\n name: string,\n args: unknown,\n ctx: ToolContext,\n tools: readonly Tool[] = TOOLS,\n) {\n return (await rpc(ctx, tools, 'tools/call', { name, arguments: args }, name)) as {\n isError?: boolean;\n structuredContent?: Record<string, unknown>;\n content: Array<{ type: string; text?: string }>;\n };\n}\n\nexport type ToolSpec = [\n name: string,\n scope: AccessScope,\n kind: ToolKind,\n idempotent: boolean,\n openWorld: boolean,\n];\n\nexport function toolSpecs(tools: readonly Tool[]): ToolSpec[] {\n return tools.map((tool) => [tool.name, tool.scope, tool.kind, tool.idempotent, tool.openWorld]);\n}\n\nexport type ToolCase = {\n tool: string;\n args: Record<string, unknown>;\n service: string;\n expected: unknown[];\n returns?: unknown;\n shown?: unknown;\n};\n\nfunction serviceFake(path: string, returns: unknown, calls: unknown[][]): Record<string, unknown> {\n const keys = path.split('.');\n const method = keys.pop() ?? '';\n let node: Record<string, unknown> = {\n [method]: async (...args: unknown[]) => {\n calls.push(args);\n return returns;\n },\n };\n for (const key of keys.reverse()) {\n node = { [key]: node };\n }\n return node;\n}\n\nexport async function runCase(tools: readonly Tool[], c: ToolCase): Promise<void> {\n const calls: unknown[][] = [];\n const returns = 'returns' in c ? c.returns : { id: 'result' };\n const ctx = toolContext(fakeContainer(serviceFake(c.service, returns, calls)));\n const result = await callTool(c.tool, c.args, ctx, tools);\n expect(result.isError ?? false).toBe(false);\n expect(calls).toEqual([c.expected]);\n expect(result.structuredContent).toEqual(\n toToolResult('shown' in c ? c.shown : returns).structuredContent,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"test-support.js","names":[],"sources":["../../../src/http/mcp/test-support.ts"],"sourcesContent":["import { expect } from 'vitest';\nimport { createMcpHandler } from '@modelcontextprotocol/server';\nimport { ALL_PERMISSIONS, actorWith } from '@headless-lms/core/organizations';\nimport type { Actor } from '@headless-lms/core/shared/actor';\nimport type { CallerLimit } from '@headless-lms/core/organizations';\nimport type { Container } from '../../app/container.js';\nimport { mcpEndpoints } from '../../app/mcp-endpoints.js';\nimport { createToolServer } from './server.js';\nimport { toToolResult, type Tool, type ToolContext, type ToolKind } from './tool.js';\nimport { TOOLS } from './tools/index.js';\n\nexport const ORG = { id: 'org_1', name: 'Acme', slug: 'acme', externalId: 'org_1' };\nexport const USER = {\n id: 'usr_1',\n email: 'owner@acme.test',\n firstName: 'Ada',\n lastName: 'Lovelace',\n externalId: 'usr_1',\n};\nexport const ORG_USER = { id: 'ou_1', orgId: 'org_1', userId: 'usr_1', status: 'active' };\nexport const OWNER: Actor = actorWith({ id: ORG_USER.id, orgId: ORG.id, owner: true });\n\ntype Lookups = {\n getByExternalId: (id: string) => Promise<{ id: string } | null>;\n getUserByExternalId: (id: string) => Promise<{ id: string } | null>;\n getOrgUser: (orgId: string, userId: string) => Promise<{ id: string } | null>;\n actorFor: (orgId: string, orgUserId: string, limit?: CallerLimit) => Promise<Actor | null>;\n};\n\nexport function fakeContainer(overrides: Record<string, unknown> = {}): Container {\n const logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n child: () => logger,\n };\n const base: Record<string, unknown> = {\n mcp: mcpEndpoints({\n authBaseURL: 'http://localhost:8100',\n adminAppUrl: 'http://localhost:8101',\n }),\n logger,\n requestContext: { set: () => {}, run: (_ctx: unknown, fn: () => void) => fn() },\n auth: {\n verifyAccess: async () => ({\n userId: USER.id,\n orgId: ORG.id,\n clientId: 'client_1',\n limit: { scopes: [...ALL_PERMISSIONS] },\n }),\n },\n organizations: {\n getById: async (id: string) => (id === ORG.id ? ORG : null),\n getByExternalId: async (id: string) => (id === ORG.externalId ? ORG : null),\n getOrgUser: async (orgId: string, userId: string) =>\n orgId === ORG.id && userId === USER.id ? ORG_USER : null,\n actorFor: async (orgId: string, orgUserId: string) =>\n orgId === ORG.id && orgUserId === ORG_USER.id ? OWNER : null,\n resolveCaller: async (authOrgId: string, authUserId: string, limit?: CallerLimit) => {\n const orgs = base.organizations as Lookups;\n const people = base.identity as Pick<Lookups, 'getUserByExternalId'>;\n const org = await orgs.getByExternalId(authOrgId);\n if (!org) {\n return null;\n }\n const user = await people.getUserByExternalId(authUserId);\n const orgUser = user ? await orgs.getOrgUser(org.id, user.id) : null;\n const actor = orgUser ? await orgs.actorFor(org.id, orgUser.id, limit) : null;\n return { orgId: org.id, userId: user?.id ?? null, orgUserId: orgUser?.id ?? null, actor };\n },\n },\n identity: {\n getUserByExternalId: async (id: string) => (id === USER.id ? USER : null),\n getUserById: async (id: string) => (id === USER.id ? USER : null),\n },\n };\n for (const [key, value] of Object.entries(overrides)) {\n const current = base[key];\n base[key] =\n current &&\n typeof current === 'object' &&\n value &&\n typeof value === 'object' &&\n !Array.isArray(value)\n ? { ...(current as object), ...(value as object) }\n : value;\n }\n return base as unknown as Container;\n}\n\nexport function toolContext(container: Container, actor: Actor = OWNER): ToolContext {\n return {\n container,\n caller: {\n actor,\n orgId: ORG.id,\n userId: USER.id,\n orgUserId: ORG_USER.id,\n authOrgId: ORG.id,\n authUserId: USER.id,\n },\n requestId: 'req_test',\n };\n}\n\nconst MODERN_META = {\n 'io.modelcontextprotocol/protocolVersion': '2026-07-28',\n 'io.modelcontextprotocol/clientCapabilities': {},\n};\n\nasync function rpc(\n ctx: ToolContext,\n tools: readonly Tool[],\n method: string,\n params: Record<string, unknown>,\n name?: string,\n) {\n const handler = createMcpHandler(() => createToolServer(ctx, tools));\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n 'mcp-protocol-version': '2026-07-28',\n 'mcp-method': method,\n };\n if (name) {\n headers['mcp-name'] = name;\n }\n const response = await handler.fetch(\n new Request('http://localhost:8100/mcp', {\n method: 'POST',\n headers,\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method,\n params: { ...params, _meta: MODERN_META },\n }),\n }),\n );\n const body = (await response.json()) as {\n result?: Record<string, unknown>;\n error?: { message: string };\n };\n if (body.error) {\n throw new Error(body.error.message);\n }\n return body.result as Record<string, unknown>;\n}\n\nexport async function listTools(ctx: ToolContext, tools: readonly Tool[] = TOOLS) {\n const result = await rpc(ctx, tools, 'tools/list', {});\n return result.tools as Array<{\n name: string;\n annotations?: Record<string, unknown>;\n inputSchema: { type?: string; properties?: Record<string, unknown> };\n }>;\n}\n\nexport async function callTool(\n name: string,\n args: unknown,\n ctx: ToolContext,\n tools: readonly Tool[] = TOOLS,\n) {\n return (await rpc(ctx, tools, 'tools/call', { name, arguments: args }, name)) as {\n isError?: boolean;\n structuredContent?: Record<string, unknown>;\n content: Array<{ type: string; text?: string }>;\n };\n}\n\nexport type ToolSpec = [name: string, kind: ToolKind, idempotent: boolean, openWorld: boolean];\n\nexport function toolSpecs(tools: readonly Tool[]): ToolSpec[] {\n return tools.map((tool) => [tool.name, tool.kind, tool.idempotent, tool.openWorld]);\n}\n\nexport type ToolCase = {\n tool: string;\n args: Record<string, unknown>;\n service: string;\n expected: unknown[];\n returns?: unknown;\n shown?: unknown;\n};\n\nfunction serviceFake(path: string, returns: unknown, calls: unknown[][]): Record<string, unknown> {\n const keys = path.split('.');\n const method = keys.pop() ?? '';\n let node: Record<string, unknown> = {\n [method]: async (...args: unknown[]) => {\n calls.push(args);\n return returns;\n },\n };\n for (const key of keys.reverse()) {\n node = { [key]: node };\n }\n return node;\n}\n\nexport async function runCase(tools: readonly Tool[], c: ToolCase): Promise<void> {\n const calls: unknown[][] = [];\n const returns = 'returns' in c ? c.returns : { id: 'result' };\n const ctx = toolContext(fakeContainer(serviceFake(c.service, returns, calls)));\n const result = await callTool(c.tool, c.args, ctx, tools);\n expect(result.isError ?? false).toBe(false);\n expect(calls).toEqual([c.expected]);\n expect(result.structuredContent).toEqual(\n toToolResult('shown' in c ? c.shown : returns).structuredContent,\n );\n}\n"],"mappings":";;;;;;;;AAWA,MAAa,MAAM;CAAE,IAAI;CAAS,MAAM;CAAQ,MAAM;CAAQ,YAAY;AAAQ;AAClF,MAAa,OAAO;CAClB,IAAI;CACJ,OAAO;CACP,WAAW;CACX,UAAU;CACV,YAAY;AACd;AACA,MAAa,WAAW;CAAE,IAAI;CAAQ,OAAO;CAAS,QAAQ;CAAS,QAAQ;AAAS;AACxF,MAAa,QAAe,UAAU;CAAE,IAAI,SAAS;CAAI,OAAO,IAAI;CAAI,OAAO;AAAK,CAAC;AASrF,SAAgB,cAAc,YAAqC,CAAC,GAAc;CAChF,MAAM,SAAS;EACb,aAAa,CAAC;EACd,YAAY,CAAC;EACb,YAAY,CAAC;EACb,aAAa,CAAC;EACd,aAAa;CACf;CACA,MAAM,OAAgC;EACpC,KAAK,aAAa;GAChB,aAAa;GACb,aAAa;EACf,CAAC;EACD;EACA,gBAAgB;GAAE,WAAW,CAAC;GAAG,MAAM,MAAe,OAAmB,GAAG;EAAE;EAC9E,MAAM,EACJ,cAAc,aAAa;GACzB,QAAQ,KAAK;GACb,OAAO,IAAI;GACX,UAAU;GACV,OAAO,EAAE,QAAQ,CAAC,GAAG,eAAe,EAAE;EACxC,GACF;EACA,eAAe;GACb,SAAS,OAAO,OAAgB,OAAO,IAAI,KAAK,MAAM;GACtD,iBAAiB,OAAO,OAAgB,OAAO,IAAI,aAAa,MAAM;GACtE,YAAY,OAAO,OAAe,WAChC,UAAU,IAAI,MAAM,WAAW,KAAK,KAAK,WAAW;GACtD,UAAU,OAAO,OAAe,cAC9B,UAAU,IAAI,MAAM,cAAc,SAAS,KAAK,QAAQ;GAC1D,eAAe,OAAO,WAAmB,YAAoB,UAAwB;IACnF,MAAM,OAAO,KAAK;IAClB,MAAM,SAAS,KAAK;IACpB,MAAM,MAAM,MAAM,KAAK,gBAAgB,SAAS;IAChD,IAAI,CAAC,KACH,OAAO;IAET,MAAM,OAAO,MAAM,OAAO,oBAAoB,UAAU;IACxD,MAAM,UAAU,OAAO,MAAM,KAAK,WAAW,IAAI,IAAI,KAAK,EAAE,IAAI;IAChE,MAAM,QAAQ,UAAU,MAAM,KAAK,SAAS,IAAI,IAAI,QAAQ,IAAI,KAAK,IAAI;IACzE,OAAO;KAAE,OAAO,IAAI;KAAI,QAAQ,MAAM,MAAM;KAAM,WAAW,SAAS,MAAM;KAAM;IAAM;GAC1F;EACF;EACA,UAAU;GACR,qBAAqB,OAAO,OAAgB,OAAO,KAAK,KAAK,OAAO;GACpE,aAAa,OAAO,OAAgB,OAAO,KAAK,KAAK,OAAO;EAC9D;CACF;CACA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,GAAG;EACpD,MAAM,UAAU,KAAK;EACrB,KAAK,OACH,WACA,OAAO,YAAY,YACnB,SACA,OAAO,UAAU,YACjB,CAAC,MAAM,QAAQ,KAAK,IAChB;GAAE,GAAI;GAAoB,GAAI;EAAiB,IAC/C;CACR;CACA,OAAO;AACT;AAEA,SAAgB,YAAY,WAAsB,QAAe,OAAoB;CACnF,OAAO;EACL;EACA,QAAQ;GACN;GACA,OAAO,IAAI;GACX,QAAQ,KAAK;GACb,WAAW,SAAS;GACpB,WAAW,IAAI;GACf,YAAY,KAAK;EACnB;EACA,WAAW;CACb;AACF;AAEA,MAAM,cAAc;CAClB,2CAA2C;CAC3C,8CAA8C,CAAC;AACjD;AAEA,eAAe,IACb,KACA,OACA,QACA,QACA,MACA;CACA,MAAM,UAAU,uBAAuB,iBAAiB,KAAK,KAAK,CAAC;CACnE,MAAM,UAAkC;EACtC,gBAAgB;EAChB,wBAAwB;EACxB,cAAc;CAChB;CACA,IAAI,MACF,QAAQ,cAAc;CAcxB,MAAM,OAAQ,OAAM,MAZG,QAAQ,MAC7B,IAAI,QAAQ,6BAA6B;EACvC,QAAQ;EACR;EACA,MAAM,KAAK,UAAU;GACnB,SAAS;GACT,IAAI;GACJ;GACA,QAAQ;IAAE,GAAG;IAAQ,OAAO;GAAY;EAC1C,CAAC;CACH,CAAC,CACH,EAAA,CAC6B,KAAK;CAIlC,IAAI,KAAK,OACP,MAAM,IAAI,MAAM,KAAK,MAAM,OAAO;CAEpC,OAAO,KAAK;AACd;AAEA,eAAsB,UAAU,KAAkB,QAAyB,OAAO;CAEhF,QAAO,MADc,IAAI,KAAK,OAAO,cAAc,CAAC,CAAC,EAAA,CACvC;AAKhB;AAEA,eAAsB,SACpB,MACA,MACA,KACA,QAAyB,OACzB;CACA,OAAQ,MAAM,IAAI,KAAK,OAAO,cAAc;EAAE;EAAM,WAAW;CAAK,GAAG,IAAI;AAK7E;AAIA,SAAgB,UAAU,OAAoC;CAC5D,OAAO,MAAM,KAAK,SAAS;EAAC,KAAK;EAAM,KAAK;EAAM,KAAK;EAAY,KAAK;CAAS,CAAC;AACpF;AAWA,SAAS,YAAY,MAAc,SAAkB,OAA6C;CAChG,MAAM,OAAO,KAAK,MAAM,GAAG;CAE3B,IAAI,OAAgC,GADrB,KAAK,IAAI,KAAK,KAEjB,OAAO,GAAG,SAAoB;EACtC,MAAM,KAAK,IAAI;EACf,OAAO;CACT,EACF;CACA,KAAK,MAAM,OAAO,KAAK,QAAQ,GAC7B,OAAO,GAAG,MAAM,KAAK;CAEvB,OAAO;AACT;AAEA,eAAsB,QAAQ,OAAwB,GAA4B;CAChF,MAAM,QAAqB,CAAC;CAC5B,MAAM,UAAU,aAAa,IAAI,EAAE,UAAU,EAAE,IAAI,SAAS;CAC5D,MAAM,MAAM,YAAY,cAAc,YAAY,EAAE,SAAS,SAAS,KAAK,CAAC,CAAC;CAC7E,MAAM,SAAS,MAAM,SAAS,EAAE,MAAM,EAAE,MAAM,KAAK,KAAK;CACxD,aAAO,OAAO,WAAW,KAAK,CAAC,CAAC,KAAK,KAAK;CAC1C,aAAO,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;CAClC,aAAO,OAAO,iBAAiB,CAAC,CAAC,QAC/B,aAAa,WAAW,IAAI,EAAE,QAAQ,OAAO,CAAC,CAAC,iBACjD;AACF"}
@@ -1,11 +1,10 @@
1
1
  import { Container } from "../../app/container.js";
2
- import { OrgScope } from "../scope.js";
3
- import { AccessScope } from "@headless-lms/core/organizations";
2
+ import { Caller } from "../scope.js";
4
3
  import { z } from "zod";
5
4
  //#region src/http/mcp/tool.d.ts
6
5
  interface ToolContext {
7
6
  container: Container;
8
- scope: OrgScope;
7
+ caller: Caller;
9
8
  requestId: string;
10
9
  }
11
10
  type ToolKind = 'read' | 'write' | 'destructive';
@@ -13,7 +12,6 @@ interface Tool {
13
12
  name: string;
14
13
  title: string;
15
14
  description: string;
16
- scope: AccessScope;
17
15
  kind: ToolKind;
18
16
  idempotent: boolean;
19
17
  openWorld: boolean;
@@ -26,10 +24,6 @@ interface ToolDef<Input extends z.ZodObject> extends Omit<Tool, 'input' | 'call'
26
24
  call(input: z.output<Input>, ctx: ToolContext): unknown;
27
25
  }
28
26
  declare const noInput: z.ZodObject<{}, z.core.$strip>;
29
- declare const actor: (scope: OrgScope) => {
30
- id: string;
31
- role: import("@headless-lms/core/organizations").StaffRole;
32
- };
33
27
  declare function defineTool<Input extends z.ZodObject>(def: ToolDef<Input>): Tool;
34
28
  interface ToolResult {
35
29
  [key: string]: unknown;
@@ -44,5 +38,5 @@ declare function toolError(message: string): ToolResult;
44
38
  declare function toToolResult(data: unknown): ToolResult;
45
39
  declare function runTool(tool: Tool, args: unknown, ctx: ToolContext): Promise<ToolResult>;
46
40
  //#endregion
47
- export { Tool, ToolContext, ToolDef, ToolKind, ToolResult, actor, defineTool, noInput, runTool, toToolResult, toolError };
41
+ export { Tool, ToolContext, ToolDef, ToolKind, ToolResult, defineTool, noInput, runTool, toToolResult, toolError };
48
42
  //# sourceMappingURL=tool.d.ts.map
@@ -1,11 +1,8 @@
1
1
  import { classifyError } from "../errors.js";
2
+ import { refusalSubject } from "../plugins/error-handler.js";
2
3
  import { z } from "zod";
3
4
  //#region src/http/mcp/tool.ts
4
5
  const noInput = z.object({});
5
- const actor = (scope) => ({
6
- id: scope.orgUserId,
7
- role: scope.role
8
- });
9
6
  function defineTool(def) {
10
7
  return {
11
8
  ...def,
@@ -34,7 +31,18 @@ function toToolResult(data) {
34
31
  }
35
32
  function describeFailure(error, tool, ctx) {
36
33
  const known = classifyError(error);
37
- if (known) return known.message;
34
+ if (known) {
35
+ ctx.container.logger.info("mcp tool refused", {
36
+ tool: tool.name,
37
+ status: known.status,
38
+ error: known.error,
39
+ message: known.message,
40
+ orgId: ctx.caller.orgId,
41
+ actorId: ctx.caller.orgUserId,
42
+ ...refusalSubject(error)
43
+ });
44
+ return known.message;
45
+ }
38
46
  ctx.container.logger.error("mcp tool failed", {
39
47
  err: error instanceof Error ? error : new Error(String(error)),
40
48
  tool: tool.name
@@ -53,6 +61,6 @@ async function runTool(tool, args, ctx) {
53
61
  }
54
62
  }
55
63
  //#endregion
56
- export { actor, defineTool, noInput, runTool, toToolResult, toolError };
64
+ export { defineTool, noInput, runTool, toToolResult, toolError };
57
65
 
58
66
  //# sourceMappingURL=tool.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool.js","names":[],"sources":["../../../src/http/mcp/tool.ts"],"sourcesContent":["import { z } from 'zod';\nimport type { AccessScope } from '@headless-lms/core/organizations';\nimport type { Container } from '../../app/container.js';\nimport { classifyError } from '../errors.js';\nimport type { OrgScope } from '../scope.js';\n\nexport interface ToolContext {\n container: Container;\n scope: OrgScope;\n requestId: string;\n}\n\nexport type ToolKind = 'read' | 'write' | 'destructive';\n\nexport interface Tool {\n name: string;\n title: string;\n description: string;\n scope: AccessScope;\n kind: ToolKind;\n idempotent: boolean;\n openWorld: boolean;\n input: z.ZodObject;\n output?: z.ZodType;\n call(input: unknown, ctx: ToolContext): unknown;\n}\n\nexport interface ToolDef<Input extends z.ZodObject> extends Omit<Tool, 'input' | 'call'> {\n input: Input;\n call(input: z.output<Input>, ctx: ToolContext): unknown;\n}\n\nexport const noInput = z.object({});\n\nexport const actor = (scope: OrgScope) => ({ id: scope.orgUserId, role: scope.role });\n\nexport function defineTool<Input extends z.ZodObject>(def: ToolDef<Input>): Tool {\n return { ...def, call: (input, ctx) => def.call(input as z.output<Input>, ctx) };\n}\n\nexport interface ToolResult {\n [key: string]: unknown;\n content: Array<{ type: 'text'; text: string }>;\n structuredContent?: Record<string, unknown>;\n isError?: boolean;\n}\n\nexport function toolError(message: string): ToolResult {\n return { content: [{ type: 'text', text: message }], isError: true };\n}\n\nexport function toToolResult(data: unknown): ToolResult {\n const plain: unknown = JSON.parse(JSON.stringify(data === undefined ? { ok: true } : data));\n const structuredContent =\n plain !== null && typeof plain === 'object' && !Array.isArray(plain)\n ? (plain as Record<string, unknown>)\n : { result: plain };\n return {\n content: [{ type: 'text', text: JSON.stringify(structuredContent) }],\n structuredContent,\n };\n}\n\nfunction describeFailure(error: unknown, tool: Tool, ctx: ToolContext): string {\n const known = classifyError(error);\n if (known) {\n return known.message;\n }\n ctx.container.logger.error('mcp tool failed', {\n err: error instanceof Error ? error : new Error(String(error)),\n tool: tool.name,\n });\n return `Internal error (request ${ctx.requestId})`;\n}\n\nexport async function runTool(tool: Tool, args: unknown, ctx: ToolContext): Promise<ToolResult> {\n const input = tool.input.safeParse(args);\n if (!input.success) {\n return toolError(`Invalid input: ${z.prettifyError(input.error)}`);\n }\n try {\n const result = await tool.call(input.data, ctx);\n if (result === null || result === false) {\n return toolError('Not found.');\n }\n return toToolResult(tool.output && result !== undefined ? tool.output.parse(result) : result);\n } catch (error) {\n return toolError(describeFailure(error, tool, ctx));\n }\n}\n"],"mappings":";;;AAgCA,MAAa,UAAU,EAAE,OAAO,CAAC,CAAC;AAElC,MAAa,SAAS,WAAqB;CAAE,IAAI,MAAM;CAAW,MAAM,MAAM;AAAK;AAEnF,SAAgB,WAAsC,KAA2B;CAC/E,OAAO;EAAE,GAAG;EAAK,OAAO,OAAO,QAAQ,IAAI,KAAK,OAA0B,GAAG;CAAE;AACjF;AASA,SAAgB,UAAU,SAA6B;CACrD,OAAO;EAAE,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM;EAAQ,CAAC;EAAG,SAAS;CAAK;AACrE;AAEA,SAAgB,aAAa,MAA2B;CACtD,MAAM,QAAiB,KAAK,MAAM,KAAK,UAAU,SAAS,KAAA,IAAY,EAAE,IAAI,KAAK,IAAI,IAAI,CAAC;CAC1F,MAAM,oBACJ,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAC9D,QACD,EAAE,QAAQ,MAAM;CACtB,OAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,iBAAiB;EAAE,CAAC;EACnE;CACF;AACF;AAEA,SAAS,gBAAgB,OAAgB,MAAY,KAA0B;CAC7E,MAAM,QAAQ,cAAc,KAAK;CACjC,IAAI,OACF,OAAO,MAAM;CAEf,IAAI,UAAU,OAAO,MAAM,mBAAmB;EAC5C,KAAK,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;EAC7D,MAAM,KAAK;CACb,CAAC;CACD,OAAO,2BAA2B,IAAI,UAAU;AAClD;AAEA,eAAsB,QAAQ,MAAY,MAAe,KAAuC;CAC9F,MAAM,QAAQ,KAAK,MAAM,UAAU,IAAI;CACvC,IAAI,CAAC,MAAM,SACT,OAAO,UAAU,kBAAkB,EAAE,cAAc,MAAM,KAAK,GAAG;CAEnE,IAAI;EACF,MAAM,SAAS,MAAM,KAAK,KAAK,MAAM,MAAM,GAAG;EAC9C,IAAI,WAAW,QAAQ,WAAW,OAChC,OAAO,UAAU,YAAY;EAE/B,OAAO,aAAa,KAAK,UAAU,WAAW,KAAA,IAAY,KAAK,OAAO,MAAM,MAAM,IAAI,MAAM;CAC9F,SAAS,OAAO;EACd,OAAO,UAAU,gBAAgB,OAAO,MAAM,GAAG,CAAC;CACpD;AACF"}
1
+ {"version":3,"file":"tool.js","names":[],"sources":["../../../src/http/mcp/tool.ts"],"sourcesContent":["import { z } from 'zod';\nimport type { Container } from '../../app/container.js';\nimport { classifyError } from '../errors.js';\nimport { refusalSubject } from '../plugins/error-handler.js';\nimport type { Caller } from '../scope.js';\n\nexport interface ToolContext {\n container: Container;\n caller: Caller;\n requestId: string;\n}\n\nexport type ToolKind = 'read' | 'write' | 'destructive';\n\nexport interface Tool {\n name: string;\n title: string;\n description: string;\n kind: ToolKind;\n idempotent: boolean;\n openWorld: boolean;\n input: z.ZodObject;\n output?: z.ZodType;\n call(input: unknown, ctx: ToolContext): unknown;\n}\n\nexport interface ToolDef<Input extends z.ZodObject> extends Omit<Tool, 'input' | 'call'> {\n input: Input;\n call(input: z.output<Input>, ctx: ToolContext): unknown;\n}\n\nexport const noInput = z.object({});\n\nexport function defineTool<Input extends z.ZodObject>(def: ToolDef<Input>): Tool {\n return { ...def, call: (input, ctx) => def.call(input as z.output<Input>, ctx) };\n}\n\nexport interface ToolResult {\n [key: string]: unknown;\n content: Array<{ type: 'text'; text: string }>;\n structuredContent?: Record<string, unknown>;\n isError?: boolean;\n}\n\nexport function toolError(message: string): ToolResult {\n return { content: [{ type: 'text', text: message }], isError: true };\n}\n\nexport function toToolResult(data: unknown): ToolResult {\n const plain: unknown = JSON.parse(JSON.stringify(data === undefined ? { ok: true } : data));\n const structuredContent =\n plain !== null && typeof plain === 'object' && !Array.isArray(plain)\n ? (plain as Record<string, unknown>)\n : { result: plain };\n return {\n content: [{ type: 'text', text: JSON.stringify(structuredContent) }],\n structuredContent,\n };\n}\n\nfunction describeFailure(error: unknown, tool: Tool, ctx: ToolContext): string {\n const known = classifyError(error);\n if (known) {\n ctx.container.logger.info('mcp tool refused', {\n tool: tool.name,\n status: known.status,\n error: known.error,\n message: known.message,\n orgId: ctx.caller.orgId,\n actorId: ctx.caller.orgUserId,\n ...refusalSubject(error),\n });\n return known.message;\n }\n ctx.container.logger.error('mcp tool failed', {\n err: error instanceof Error ? error : new Error(String(error)),\n tool: tool.name,\n });\n return `Internal error (request ${ctx.requestId})`;\n}\n\nexport async function runTool(tool: Tool, args: unknown, ctx: ToolContext): Promise<ToolResult> {\n const input = tool.input.safeParse(args);\n if (!input.success) {\n return toolError(`Invalid input: ${z.prettifyError(input.error)}`);\n }\n try {\n const result = await tool.call(input.data, ctx);\n if (result === null || result === false) {\n return toolError('Not found.');\n }\n return toToolResult(tool.output && result !== undefined ? tool.output.parse(result) : result);\n } catch (error) {\n return toolError(describeFailure(error, tool, ctx));\n }\n}\n"],"mappings":";;;;AA+BA,MAAa,UAAU,EAAE,OAAO,CAAC,CAAC;AAElC,SAAgB,WAAsC,KAA2B;CAC/E,OAAO;EAAE,GAAG;EAAK,OAAO,OAAO,QAAQ,IAAI,KAAK,OAA0B,GAAG;CAAE;AACjF;AASA,SAAgB,UAAU,SAA6B;CACrD,OAAO;EAAE,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM;EAAQ,CAAC;EAAG,SAAS;CAAK;AACrE;AAEA,SAAgB,aAAa,MAA2B;CACtD,MAAM,QAAiB,KAAK,MAAM,KAAK,UAAU,SAAS,KAAA,IAAY,EAAE,IAAI,KAAK,IAAI,IAAI,CAAC;CAC1F,MAAM,oBACJ,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAC9D,QACD,EAAE,QAAQ,MAAM;CACtB,OAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,iBAAiB;EAAE,CAAC;EACnE;CACF;AACF;AAEA,SAAS,gBAAgB,OAAgB,MAAY,KAA0B;CAC7E,MAAM,QAAQ,cAAc,KAAK;CACjC,IAAI,OAAO;EACT,IAAI,UAAU,OAAO,KAAK,oBAAoB;GAC5C,MAAM,KAAK;GACX,QAAQ,MAAM;GACd,OAAO,MAAM;GACb,SAAS,MAAM;GACf,OAAO,IAAI,OAAO;GAClB,SAAS,IAAI,OAAO;GACpB,GAAG,eAAe,KAAK;EACzB,CAAC;EACD,OAAO,MAAM;CACf;CACA,IAAI,UAAU,OAAO,MAAM,mBAAmB;EAC5C,KAAK,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;EAC7D,MAAM,KAAK;CACb,CAAC;CACD,OAAO,2BAA2B,IAAI,UAAU;AAClD;AAEA,eAAsB,QAAQ,MAAY,MAAe,KAAuC;CAC9F,MAAM,QAAQ,KAAK,MAAM,UAAU,IAAI;CACvC,IAAI,CAAC,MAAM,SACT,OAAO,UAAU,kBAAkB,EAAE,cAAc,MAAM,KAAK,GAAG;CAEnE,IAAI;EACF,MAAM,SAAS,MAAM,KAAK,KAAK,MAAM,MAAM,GAAG;EAC9C,IAAI,WAAW,QAAQ,WAAW,OAChC,OAAO,UAAU,YAAY;EAE/B,OAAO,aAAa,KAAK,UAAU,WAAW,KAAA,IAAY,KAAK,OAAO,MAAM,MAAM,IAAI,MAAM;CAC9F,SAAS,OAAO;EACd,OAAO,UAAU,gBAAgB,OAAO,MAAM,GAAG,CAAC;CACpD;AACF"}
@@ -7,111 +7,101 @@ const automationTools = [
7
7
  name: "list_automation_triggers",
8
8
  title: "List workflow triggers",
9
9
  description: "The events that can start a workflow.",
10
- scope: "automations:read",
11
10
  kind: "read",
12
11
  idempotent: true,
13
12
  openWorld: false,
14
13
  input: noInput,
15
- call: (_input, { container }) => container.automations.availableTriggers()
14
+ call: (_input, { container, caller }) => container.automations.availableTriggers(caller.actor)
16
15
  }),
17
16
  defineTool({
18
17
  name: "list_automation_actions",
19
18
  title: "List workflow actions",
20
19
  description: "The actions a workflow can run, with the settings each one takes.",
21
- scope: "automations:read",
22
20
  kind: "read",
23
21
  idempotent: true,
24
22
  openWorld: false,
25
23
  input: noInput,
26
- call: (_input, { container }) => container.automations.availableActions()
24
+ call: (_input, { container, caller }) => container.automations.availableActions(caller.actor)
27
25
  }),
28
26
  defineTool({
29
27
  name: "list_automations",
30
28
  title: "List workflows and webhooks",
31
29
  description: "Workflows and webhooks with their triggers, ordered actions and enabled state. Filter by kind (workflow or webhook).",
32
- scope: "automations:read",
33
30
  kind: "read",
34
31
  idempotent: true,
35
32
  openWorld: false,
36
33
  input: AutomationsQuery,
37
- call: (input, { container, scope }) => container.automations.list(scope.orgId, input)
34
+ call: (input, { container, caller }) => container.automations.list(input, caller.actor)
38
35
  }),
39
36
  defineTool({
40
37
  name: "list_automation_runs",
41
38
  title: "List workflow and webhook runs",
42
39
  description: "Runs of one workflow or webhook, newest first, with the trigger event as a webhook would receive it and each action result.",
43
- scope: "automations:read",
44
40
  kind: "read",
45
41
  idempotent: true,
46
42
  openWorld: false,
47
43
  input: AutomationRunsQuery.extend(AutomationIdParam.shape),
48
- call: ({ id, ...query }, { container, scope }) => container.automations.listRuns(scope.orgId, id, query)
44
+ call: ({ id, ...query }, { container, caller }) => container.automations.listRuns(id, query, caller.actor)
49
45
  }),
50
46
  defineTool({
51
47
  name: "create_automation",
52
48
  title: "Create workflow",
53
49
  description: "Create a workflow from triggers and ordered actions. Use list_automation_triggers and list_automation_actions for valid values.",
54
- scope: "automations:write",
55
50
  kind: "write",
56
51
  idempotent: false,
57
52
  openWorld: false,
58
53
  input: CreateAutomationBody,
59
- call: (input, { container, scope }) => container.automations.create(scope.orgId, input)
54
+ call: (input, { container, caller }) => container.automations.create(input, caller.actor)
60
55
  }),
61
56
  defineTool({
62
57
  name: "update_automation",
63
58
  title: "Update workflow",
64
59
  description: "Change a workflow name, description, triggers or actions, or turn it on and off with enabled. Fields left out stay as they are.",
65
- scope: "automations:write",
66
60
  kind: "write",
67
61
  idempotent: true,
68
62
  openWorld: false,
69
63
  input: UpdateAutomationBody.extend(AutomationIdParam.shape),
70
- call: ({ id, ...patch }, { container, scope }) => container.automations.update(scope.orgId, id, patch)
64
+ call: ({ id, ...patch }, { container, caller }) => container.automations.update(id, patch, caller.actor)
71
65
  }),
72
66
  defineTool({
73
67
  name: "rerun_automation_run",
74
68
  title: "Re-run workflow or webhook run",
75
69
  description: "Run a finished workflow or webhook run again with its original trigger event, using the automation's current actions. Emails and webhook deliveries are sent again. The new outcome replaces the run's previous one. A run still in progress can't be re-run.",
76
- scope: "automations:write",
77
70
  kind: "write",
78
71
  idempotent: false,
79
72
  openWorld: true,
80
73
  input: AutomationRunParams,
81
- call: ({ id, runId }, { container, scope }) => container.automations.rerun(scope.orgId, id, runId)
74
+ call: ({ id, runId }, { container, caller }) => container.automations.rerun(id, runId, caller.actor)
82
75
  }),
83
76
  defineTool({
84
77
  name: "delete_automation",
85
78
  title: "Delete workflow",
86
79
  description: "Permanently delete a workflow.",
87
- scope: "automations:delete",
88
80
  kind: "destructive",
89
81
  idempotent: true,
90
82
  openWorld: false,
91
83
  input: AutomationIdParam,
92
- call: ({ id }, { container, scope }) => container.automations.delete(scope.orgId, id)
84
+ call: ({ id }, { container, caller }) => container.automations.delete(id, caller.actor)
93
85
  }),
94
86
  defineTool({
95
87
  name: "update_webhook",
96
88
  title: "Update webhook",
97
89
  description: "Change a webhook URL, events or description, or turn it on and off with enabled. Fields left out stay as they are.",
98
- scope: "automations:write",
99
90
  kind: "write",
100
91
  idempotent: true,
101
92
  openWorld: false,
102
93
  input: UpdateWebhookBody.extend(AutomationIdParam.shape),
103
- call: ({ id, ...patch }, { container, scope }) => container.automations.updateWebhook(scope.orgId, id, patch)
94
+ call: ({ id, ...patch }, { container, caller }) => container.automations.updateWebhook(id, patch, caller.actor)
104
95
  }),
105
96
  defineTool({
106
97
  name: "delete_webhook",
107
98
  title: "Delete webhook",
108
99
  description: "Permanently delete a webhook and its signing secret. Its delivery history is kept.",
109
- scope: "automations:delete",
110
100
  kind: "destructive",
111
101
  idempotent: true,
112
102
  openWorld: false,
113
103
  input: AutomationIdParam,
114
- call: ({ id }, { container, scope }) => container.automations.deleteWebhook(scope.orgId, id)
104
+ call: ({ id }, { container, caller }) => container.automations.deleteWebhook(id, caller.actor)
115
105
  })
116
106
  ];
117
107
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"automations.js","names":[],"sources":["../../../../src/http/mcp/tools/automations.ts"],"sourcesContent":["import {\n AutomationIdParam,\n AutomationRunParams,\n AutomationRunsQuery,\n AutomationsQuery,\n CreateAutomationBody,\n UpdateAutomationBody,\n UpdateWebhookBody,\n} from '../../schemas/index.js';\nimport { defineTool, noInput, type Tool } from '../tool.js';\n\nexport const automationTools: Tool[] = [\n defineTool({\n name: 'list_automation_triggers',\n title: 'List workflow triggers',\n description: 'The events that can start a workflow.',\n scope: 'automations:read',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: noInput,\n call: (_input, { container }) => container.automations.availableTriggers(),\n }),\n defineTool({\n name: 'list_automation_actions',\n title: 'List workflow actions',\n description: 'The actions a workflow can run, with the settings each one takes.',\n scope: 'automations:read',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: noInput,\n call: (_input, { container }) => container.automations.availableActions(),\n }),\n defineTool({\n name: 'list_automations',\n title: 'List workflows and webhooks',\n description:\n 'Workflows and webhooks with their triggers, ordered actions and enabled state. Filter by kind (workflow or webhook).',\n scope: 'automations:read',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: AutomationsQuery,\n call: (input, { container, scope }) => container.automations.list(scope.orgId, input),\n }),\n defineTool({\n name: 'list_automation_runs',\n title: 'List workflow and webhook runs',\n description:\n 'Runs of one workflow or webhook, newest first, with the trigger event as a webhook would receive it and each action result.',\n scope: 'automations:read',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: AutomationRunsQuery.extend(AutomationIdParam.shape),\n call: ({ id, ...query }, { container, scope }) =>\n container.automations.listRuns(scope.orgId, id, query),\n }),\n defineTool({\n name: 'create_automation',\n title: 'Create workflow',\n description:\n 'Create a workflow from triggers and ordered actions. Use list_automation_triggers and list_automation_actions for valid values.',\n scope: 'automations:write',\n kind: 'write',\n idempotent: false,\n openWorld: false,\n input: CreateAutomationBody,\n call: (input, { container, scope }) => container.automations.create(scope.orgId, input),\n }),\n defineTool({\n name: 'update_automation',\n title: 'Update workflow',\n description:\n 'Change a workflow name, description, triggers or actions, or turn it on and off with enabled. Fields left out stay as they are.',\n scope: 'automations:write',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: UpdateAutomationBody.extend(AutomationIdParam.shape),\n call: ({ id, ...patch }, { container, scope }) =>\n container.automations.update(scope.orgId, id, patch),\n }),\n defineTool({\n name: 'rerun_automation_run',\n title: 'Re-run workflow or webhook run',\n description:\n \"Run a finished workflow or webhook run again with its original trigger event, using the automation's current actions. Emails and webhook deliveries are sent again. The new outcome replaces the run's previous one. A run still in progress can't be re-run.\",\n scope: 'automations:write',\n kind: 'write',\n idempotent: false,\n openWorld: true,\n input: AutomationRunParams,\n call: ({ id, runId }, { container, scope }) =>\n container.automations.rerun(scope.orgId, id, runId),\n }),\n defineTool({\n name: 'delete_automation',\n title: 'Delete workflow',\n description: 'Permanently delete a workflow.',\n scope: 'automations:delete',\n kind: 'destructive',\n idempotent: true,\n openWorld: false,\n input: AutomationIdParam,\n call: ({ id }, { container, scope }) => container.automations.delete(scope.orgId, id),\n }),\n defineTool({\n name: 'update_webhook',\n title: 'Update webhook',\n description:\n 'Change a webhook URL, events or description, or turn it on and off with enabled. Fields left out stay as they are.',\n scope: 'automations:write',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: UpdateWebhookBody.extend(AutomationIdParam.shape),\n call: ({ id, ...patch }, { container, scope }) =>\n container.automations.updateWebhook(scope.orgId, id, patch),\n }),\n defineTool({\n name: 'delete_webhook',\n title: 'Delete webhook',\n description:\n 'Permanently delete a webhook and its signing secret. Its delivery history is kept.',\n scope: 'automations:delete',\n kind: 'destructive',\n idempotent: true,\n openWorld: false,\n input: AutomationIdParam,\n call: ({ id }, { container, scope }) => container.automations.deleteWebhook(scope.orgId, id),\n }),\n];\n"],"mappings":";;;;AAWA,MAAa,kBAA0B;CACrC,WAAW;EACT,MAAM;EACN,OAAO;EACP,aAAa;EACb,OAAO;EACP,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,QAAQ,EAAE,gBAAgB,UAAU,YAAY,kBAAkB;CAC3E,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aAAa;EACb,OAAO;EACP,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,QAAQ,EAAE,gBAAgB,UAAU,YAAY,iBAAiB;CAC1E,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,OAAO;EACP,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,OAAO,EAAE,WAAW,YAAY,UAAU,YAAY,KAAK,MAAM,OAAO,KAAK;CACtF,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,OAAO;EACP,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO,oBAAoB,OAAO,kBAAkB,KAAK;EACzD,OAAO,EAAE,IAAI,GAAG,SAAS,EAAE,WAAW,YACpC,UAAU,YAAY,SAAS,MAAM,OAAO,IAAI,KAAK;CACzD,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,OAAO;EACP,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,OAAO,EAAE,WAAW,YAAY,UAAU,YAAY,OAAO,MAAM,OAAO,KAAK;CACxF,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,OAAO;EACP,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO,qBAAqB,OAAO,kBAAkB,KAAK;EAC1D,OAAO,EAAE,IAAI,GAAG,SAAS,EAAE,WAAW,YACpC,UAAU,YAAY,OAAO,MAAM,OAAO,IAAI,KAAK;CACvD,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,OAAO;EACP,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,EAAE,IAAI,SAAS,EAAE,WAAW,YACjC,UAAU,YAAY,MAAM,MAAM,OAAO,IAAI,KAAK;CACtD,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aAAa;EACb,OAAO;EACP,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,EAAE,MAAM,EAAE,WAAW,YAAY,UAAU,YAAY,OAAO,MAAM,OAAO,EAAE;CACtF,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,OAAO;EACP,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO,kBAAkB,OAAO,kBAAkB,KAAK;EACvD,OAAO,EAAE,IAAI,GAAG,SAAS,EAAE,WAAW,YACpC,UAAU,YAAY,cAAc,MAAM,OAAO,IAAI,KAAK;CAC9D,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,OAAO;EACP,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,EAAE,MAAM,EAAE,WAAW,YAAY,UAAU,YAAY,cAAc,MAAM,OAAO,EAAE;CAC7F,CAAC;AACH"}
1
+ {"version":3,"file":"automations.js","names":[],"sources":["../../../../src/http/mcp/tools/automations.ts"],"sourcesContent":["import {\n AutomationIdParam,\n AutomationRunParams,\n AutomationRunsQuery,\n AutomationsQuery,\n CreateAutomationBody,\n UpdateAutomationBody,\n UpdateWebhookBody,\n} from '../../schemas/index.js';\nimport { defineTool, noInput, type Tool } from '../tool.js';\n\nexport const automationTools: Tool[] = [\n defineTool({\n name: 'list_automation_triggers',\n title: 'List workflow triggers',\n description: 'The events that can start a workflow.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: noInput,\n call: (_input, { container, caller }) => container.automations.availableTriggers(caller.actor),\n }),\n defineTool({\n name: 'list_automation_actions',\n title: 'List workflow actions',\n description: 'The actions a workflow can run, with the settings each one takes.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: noInput,\n call: (_input, { container, caller }) => container.automations.availableActions(caller.actor),\n }),\n defineTool({\n name: 'list_automations',\n title: 'List workflows and webhooks',\n description:\n 'Workflows and webhooks with their triggers, ordered actions and enabled state. Filter by kind (workflow or webhook).',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: AutomationsQuery,\n call: (input, { container, caller }) => container.automations.list(input, caller.actor),\n }),\n defineTool({\n name: 'list_automation_runs',\n title: 'List workflow and webhook runs',\n description:\n 'Runs of one workflow or webhook, newest first, with the trigger event as a webhook would receive it and each action result.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: AutomationRunsQuery.extend(AutomationIdParam.shape),\n call: ({ id, ...query }, { container, caller }) =>\n container.automations.listRuns(id, query, caller.actor),\n }),\n defineTool({\n name: 'create_automation',\n title: 'Create workflow',\n description:\n 'Create a workflow from triggers and ordered actions. Use list_automation_triggers and list_automation_actions for valid values.',\n kind: 'write',\n idempotent: false,\n openWorld: false,\n input: CreateAutomationBody,\n call: (input, { container, caller }) => container.automations.create(input, caller.actor),\n }),\n defineTool({\n name: 'update_automation',\n title: 'Update workflow',\n description:\n 'Change a workflow name, description, triggers or actions, or turn it on and off with enabled. Fields left out stay as they are.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: UpdateAutomationBody.extend(AutomationIdParam.shape),\n call: ({ id, ...patch }, { container, caller }) =>\n container.automations.update(id, patch, caller.actor),\n }),\n defineTool({\n name: 'rerun_automation_run',\n title: 'Re-run workflow or webhook run',\n description:\n \"Run a finished workflow or webhook run again with its original trigger event, using the automation's current actions. Emails and webhook deliveries are sent again. The new outcome replaces the run's previous one. A run still in progress can't be re-run.\",\n kind: 'write',\n idempotent: false,\n openWorld: true,\n input: AutomationRunParams,\n call: ({ id, runId }, { container, caller }) =>\n container.automations.rerun(id, runId, caller.actor),\n }),\n defineTool({\n name: 'delete_automation',\n title: 'Delete workflow',\n description: 'Permanently delete a workflow.',\n kind: 'destructive',\n idempotent: true,\n openWorld: false,\n input: AutomationIdParam,\n call: ({ id }, { container, caller }) => container.automations.delete(id, caller.actor),\n }),\n defineTool({\n name: 'update_webhook',\n title: 'Update webhook',\n description:\n 'Change a webhook URL, events or description, or turn it on and off with enabled. Fields left out stay as they are.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: UpdateWebhookBody.extend(AutomationIdParam.shape),\n call: ({ id, ...patch }, { container, caller }) =>\n container.automations.updateWebhook(id, patch, caller.actor),\n }),\n defineTool({\n name: 'delete_webhook',\n title: 'Delete webhook',\n description:\n 'Permanently delete a webhook and its signing secret. Its delivery history is kept.',\n kind: 'destructive',\n idempotent: true,\n openWorld: false,\n input: AutomationIdParam,\n call: ({ id }, { container, caller }) => container.automations.deleteWebhook(id, caller.actor),\n }),\n];\n"],"mappings":";;;;AAWA,MAAa,kBAA0B;CACrC,WAAW;EACT,MAAM;EACN,OAAO;EACP,aAAa;EACb,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,QAAQ,EAAE,WAAW,aAAa,UAAU,YAAY,kBAAkB,OAAO,KAAK;CAC/F,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aAAa;EACb,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,QAAQ,EAAE,WAAW,aAAa,UAAU,YAAY,iBAAiB,OAAO,KAAK;CAC9F,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,OAAO,EAAE,WAAW,aAAa,UAAU,YAAY,KAAK,OAAO,OAAO,KAAK;CACxF,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO,oBAAoB,OAAO,kBAAkB,KAAK;EACzD,OAAO,EAAE,IAAI,GAAG,SAAS,EAAE,WAAW,aACpC,UAAU,YAAY,SAAS,IAAI,OAAO,OAAO,KAAK;CAC1D,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,OAAO,EAAE,WAAW,aAAa,UAAU,YAAY,OAAO,OAAO,OAAO,KAAK;CAC1F,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO,qBAAqB,OAAO,kBAAkB,KAAK;EAC1D,OAAO,EAAE,IAAI,GAAG,SAAS,EAAE,WAAW,aACpC,UAAU,YAAY,OAAO,IAAI,OAAO,OAAO,KAAK;CACxD,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,EAAE,IAAI,SAAS,EAAE,WAAW,aACjC,UAAU,YAAY,MAAM,IAAI,OAAO,OAAO,KAAK;CACvD,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aAAa;EACb,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,EAAE,MAAM,EAAE,WAAW,aAAa,UAAU,YAAY,OAAO,IAAI,OAAO,KAAK;CACxF,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO,kBAAkB,OAAO,kBAAkB,KAAK;EACvD,OAAO,EAAE,IAAI,GAAG,SAAS,EAAE,WAAW,aACpC,UAAU,YAAY,cAAc,IAAI,OAAO,OAAO,KAAK;CAC/D,CAAC;CACD,WAAW;EACT,MAAM;EACN,OAAO;EACP,aACE;EACF,MAAM;EACN,YAAY;EACZ,WAAW;EACX,OAAO;EACP,OAAO,EAAE,MAAM,EAAE,WAAW,aAAa,UAAU,YAAY,cAAc,IAAI,OAAO,KAAK;CAC/F,CAAC;AACH"}