@directus/api 35.2.0 → 36.0.0-rc.1

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 (192) hide show
  1. package/dist/ai/chat/models/chat-request.js +48 -48
  2. package/dist/ai/chat/models/object-request.js +6 -6
  3. package/dist/ai/chat/models/providers.js +14 -14
  4. package/dist/ai/chat/utils/parse-json-schema-7.js +22 -22
  5. package/dist/ai/mcp/server.js +44 -6
  6. package/dist/ai/mcp/utils.js +31 -0
  7. package/dist/ai/tools/assets/index.js +3 -3
  8. package/dist/ai/tools/collections/index.js +18 -18
  9. package/dist/ai/tools/fields/index.js +18 -18
  10. package/dist/ai/tools/files/index.js +18 -18
  11. package/dist/ai/tools/flows/index.js +16 -16
  12. package/dist/ai/tools/folders/index.js +18 -18
  13. package/dist/ai/tools/items/index.js +17 -17
  14. package/dist/ai/tools/operations/index.js +16 -16
  15. package/dist/ai/tools/relations/index.js +22 -22
  16. package/dist/ai/tools/schema/index.js +3 -3
  17. package/dist/ai/tools/schema.js +159 -159
  18. package/dist/ai/tools/system/index.js +3 -3
  19. package/dist/ai/tools/trigger-flow/index.js +3 -3
  20. package/dist/app.js +35 -11
  21. package/dist/auth/drivers/ldap.js +3 -1
  22. package/dist/auth/drivers/local.js +2 -0
  23. package/dist/auth/drivers/oauth2.js +3 -1
  24. package/dist/auth/drivers/openid.js +3 -1
  25. package/dist/auth/drivers/saml.js +2 -0
  26. package/dist/auth/utils/check-local-disabled.js +16 -0
  27. package/dist/auth/utils/check-sso-enabled.js +14 -0
  28. package/dist/auth.js +8 -5
  29. package/dist/cli/commands/bootstrap/index.js +3 -0
  30. package/dist/cli/commands/cache/clear.js +6 -1
  31. package/dist/cli/commands/roles/create.js +4 -1
  32. package/dist/cli/commands/users/create.js +3 -0
  33. package/dist/constants.js +8 -1
  34. package/dist/controllers/access.js +1 -1
  35. package/dist/controllers/activity.js +2 -1
  36. package/dist/controllers/assets.js +2 -0
  37. package/dist/controllers/auth.js +13 -5
  38. package/dist/controllers/collections.js +1 -1
  39. package/dist/controllers/comments.js +1 -1
  40. package/dist/controllers/dashboards.js +1 -1
  41. package/dist/controllers/fields.js +1 -1
  42. package/dist/controllers/files.js +3 -1
  43. package/dist/controllers/flows.js +6 -5
  44. package/dist/controllers/folders.js +1 -1
  45. package/dist/controllers/graphql.js +2 -0
  46. package/dist/controllers/items.js +3 -1
  47. package/dist/controllers/license.js +119 -0
  48. package/dist/controllers/mcp/index.js +38 -0
  49. package/dist/controllers/mcp/oauth-clients.js +68 -0
  50. package/dist/controllers/mcp/oauth-consent-page.js +316 -0
  51. package/dist/controllers/mcp/oauth.js +381 -0
  52. package/dist/controllers/mcp/templates/oauth-consent.liquid +62 -0
  53. package/dist/controllers/mcp/templates/oauth-error.liquid +28 -0
  54. package/dist/controllers/notifications.js +1 -1
  55. package/dist/controllers/operations.js +1 -1
  56. package/dist/controllers/panels.js +1 -1
  57. package/dist/controllers/permissions.js +1 -1
  58. package/dist/controllers/policies.js +1 -1
  59. package/dist/controllers/presets.js +1 -1
  60. package/dist/controllers/revisions.js +3 -2
  61. package/dist/controllers/roles.js +1 -1
  62. package/dist/controllers/server.js +38 -10
  63. package/dist/controllers/shares.js +1 -1
  64. package/dist/controllers/translations.js +1 -1
  65. package/dist/controllers/users.js +1 -1
  66. package/dist/controllers/utils.js +2 -2
  67. package/dist/controllers/versions.js +12 -5
  68. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +10 -1
  69. package/dist/database/get-ast-from-query/lib/parse-fields.js +2 -1
  70. package/dist/database/helpers/fn/dialects/mysql.js +7 -12
  71. package/dist/database/helpers/fn/dialects/oracle.js +3 -4
  72. package/dist/database/helpers/fn/dialects/postgres.js +4 -26
  73. package/dist/database/helpers/fn/json/mysql-json-path.js +22 -0
  74. package/dist/database/helpers/fn/json/parse-function.js +14 -6
  75. package/dist/database/helpers/fn/json/postgres-json-path.js +54 -0
  76. package/dist/database/migrations/20260110A-add-ai-provider-settings.js +4 -4
  77. package/dist/database/migrations/20260217A-null-item-versions.js +14 -0
  78. package/dist/database/migrations/20260312A-add-ai-translation-settings.js +18 -0
  79. package/dist/database/migrations/20260507A-add-licensing.js +22 -0
  80. package/dist/database/migrations/20260512A-add-autosave-revision-interval.js +14 -0
  81. package/dist/database/migrations/20260512B-add-mcp-oauth.js +87 -0
  82. package/dist/database/run-ast/lib/apply-query/filter/operator.js +116 -33
  83. package/dist/database/run-ast/lib/apply-query/index.js +4 -1
  84. package/dist/database/run-ast/lib/apply-query/sort.js +17 -7
  85. package/dist/database/run-ast/lib/get-db-query.js +21 -9
  86. package/dist/database/run-ast/lib/parse-current-level.js +2 -1
  87. package/dist/database/run-ast/run-ast.js +2 -1
  88. package/dist/database/run-ast/utils/get-column.js +2 -1
  89. package/dist/database/run-ast/utils/merge-with-parent-items.js +5 -3
  90. package/dist/extensions/lib/installation/manager.js +1 -1
  91. package/dist/extensions/lib/sandbox/register/operation.js +1 -1
  92. package/dist/extensions/lib/sync/sync.js +1 -1
  93. package/dist/extensions/manager.js +3 -3
  94. package/dist/flows.js +5 -5
  95. package/dist/license/entitlements/lib/collections.js +37 -0
  96. package/dist/license/entitlements/lib/custom-llms-enabled.js +18 -0
  97. package/dist/license/entitlements/lib/custom-permission-rules-enabled.js +41 -0
  98. package/dist/license/entitlements/lib/flows.js +29 -0
  99. package/dist/license/entitlements/lib/seats.js +103 -0
  100. package/dist/license/entitlements/lib/sso-enabled.js +45 -0
  101. package/dist/license/entitlements/manager.js +256 -0
  102. package/dist/license/index.js +4 -0
  103. package/dist/license/manager.js +505 -0
  104. package/dist/license/utils/compute-license-status.js +27 -0
  105. package/dist/license/utils/get-core-grace-expires-at.js +38 -0
  106. package/dist/license/utils/get-license-key.js +23 -0
  107. package/dist/license/utils/get-license-token.js +23 -0
  108. package/dist/license/utils/handle-license-error.js +41 -0
  109. package/dist/license/utils/is-in-core-grace-period.js +11 -0
  110. package/dist/license/utils/is-sso-bypass-allowed.js +21 -0
  111. package/dist/license/utils/use-rpc.js +33 -0
  112. package/dist/middleware/cache.js +4 -1
  113. package/dist/middleware/error-handler.js +11 -0
  114. package/dist/middleware/extract-token.js +11 -2
  115. package/dist/middleware/is-admin.js +16 -0
  116. package/dist/middleware/is-locked.js +16 -0
  117. package/dist/middleware/mcp-oauth-guard.js +23 -0
  118. package/dist/middleware/request-counter.js +5 -2
  119. package/dist/packages/types/dist/index.js +117 -122
  120. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +10 -1
  121. package/dist/permissions/utils/get-unaliased-field-key.js +2 -1
  122. package/dist/request/is-denied-ip.js +2 -0
  123. package/dist/schedules/license.js +31 -0
  124. package/dist/schedules/oauth-cleanup.js +26 -0
  125. package/dist/schedules/retention.js +1 -1
  126. package/dist/schedules/telemetry.js +4 -1
  127. package/dist/schedules/tus.js +1 -1
  128. package/dist/schedules/utils/duration-to-cron.js +36 -0
  129. package/dist/services/activity.js +15 -0
  130. package/dist/services/authentication.js +12 -5
  131. package/dist/services/collections.js +40 -10
  132. package/dist/services/fields.js +6 -6
  133. package/dist/services/flows.js +12 -0
  134. package/dist/services/graphql/resolvers/system-admin.js +2 -2
  135. package/dist/services/graphql/resolvers/system-global.js +1 -1
  136. package/dist/services/graphql/resolvers/system.js +43 -27
  137. package/dist/services/graphql/schema/get-types.js +28 -7
  138. package/dist/services/graphql/schema/parse-query.js +8 -0
  139. package/dist/services/graphql/schema/read.js +12 -0
  140. package/dist/services/graphql/types/json-filter.js +30 -0
  141. package/dist/services/index.js +6 -6
  142. package/dist/services/items.js +32 -14
  143. package/dist/services/mcp-oauth/cimd.js +307 -0
  144. package/dist/services/mcp-oauth/index.js +1185 -0
  145. package/dist/services/mcp-oauth/types/error.js +22 -0
  146. package/dist/services/mcp-oauth/utils/cimd-egress.js +182 -0
  147. package/dist/services/mcp-oauth/utils/domain.js +21 -0
  148. package/dist/services/mcp-oauth/utils/loopback.js +11 -0
  149. package/dist/services/mcp-oauth/utils/redirect.js +84 -0
  150. package/dist/services/mcp-oauth/utils/registration-debug.js +131 -0
  151. package/dist/services/payload.js +2 -1
  152. package/dist/services/permissions.js +31 -9
  153. package/dist/services/revisions.js +15 -0
  154. package/dist/services/server.js +66 -68
  155. package/dist/services/settings.js +37 -3
  156. package/dist/services/users.js +23 -6
  157. package/dist/services/utils.js +6 -1
  158. package/dist/services/versions.js +160 -70
  159. package/dist/utils/calculate-field-depth.js +1 -0
  160. package/dist/utils/create-admin.js +3 -3
  161. package/dist/utils/deep-freeze.js +24 -0
  162. package/dist/utils/extract-function-name.js +13 -0
  163. package/dist/utils/generate-translations.js +5 -5
  164. package/dist/utils/get-accountability-for-token.js +13 -1
  165. package/dist/utils/get-cache-key.js +1 -1
  166. package/dist/utils/get-history-filter-query.js +22 -0
  167. package/dist/utils/get-schema.js +2 -2
  168. package/dist/utils/get-service.js +3 -3
  169. package/dist/utils/is-admin.js +9 -0
  170. package/dist/utils/is-unauthenticated.js +15 -0
  171. package/dist/utils/parse-oauth-scope.js +12 -0
  172. package/dist/utils/sanitize-query.js +2 -2
  173. package/dist/utils/split-field-path.js +29 -0
  174. package/dist/utils/store.js +1 -1
  175. package/dist/utils/transaction.js +2 -2
  176. package/dist/utils/translations-validation.js +2 -2
  177. package/dist/utils/validate-query.js +35 -4
  178. package/dist/utils/validate-user-count-integrity.js +28 -5
  179. package/dist/utils/verify-session-jwt.js +5 -2
  180. package/dist/utils/versioning/handle-version.js +131 -48
  181. package/dist/utils/versioning/remove-circular.js +17 -0
  182. package/dist/websocket/authenticate.js +2 -1
  183. package/dist/websocket/collab/collab.js +1 -1
  184. package/dist/websocket/collab/room.js +1 -1
  185. package/dist/websocket/controllers/base.js +12 -0
  186. package/dist/websocket/controllers/graphql.js +1 -1
  187. package/dist/websocket/handlers/subscribe.js +1 -1
  188. package/dist/websocket/messages.js +64 -64
  189. package/dist/websocket/utils/items.js +2 -2
  190. package/license +90 -80
  191. package/package.json +33 -32
  192. package/dist/controllers/mcp.js +0 -31
@@ -1,79 +1,79 @@
1
1
  import { zodJsonSchema7Parser } from "../utils/zod-jsonschema7-parser.js";
2
2
  import { ProviderAnthropic, ProviderGoogle, ProviderOpenAi, ProviderOpenAiCompatible } from "./providers.js";
3
3
  import "ai";
4
- import { z } from "zod";
4
+ import { z as z$1 } from "zod";
5
5
 
6
6
  //#region src/ai/chat/models/chat-request.ts
7
- const ChatRequestTool = z.union([z.string(), z.object({
8
- name: z.string(),
9
- description: z.string(),
10
- inputSchema: z.custom(zodJsonSchema7Parser, { message: "Invalid JSON schema" })
7
+ const ChatRequestTool = z$1.union([z$1.string(), z$1.object({
8
+ name: z$1.string(),
9
+ description: z$1.string(),
10
+ inputSchema: z$1.custom(zodJsonSchema7Parser, { message: "Invalid JSON schema" })
11
11
  })]);
12
- const ToolApprovalMode = z.enum([
12
+ const ToolApprovalMode = z$1.enum([
13
13
  "always",
14
14
  "ask",
15
15
  "disabled"
16
16
  ]);
17
- const ItemContextData = z.object({
18
- collection: z.string(),
19
- key: z.union([z.string(), z.number()])
17
+ const ItemContextData = z$1.object({
18
+ collection: z$1.string(),
19
+ key: z$1.union([z$1.string(), z$1.number()])
20
20
  });
21
- const VisualElementContextData = z.object({
22
- key: z.string(),
23
- collection: z.string(),
24
- item: z.union([z.string(), z.number()]),
25
- fields: z.array(z.string()).optional(),
26
- rect: z.object({
27
- top: z.number(),
28
- left: z.number(),
29
- width: z.number(),
30
- height: z.number()
21
+ const VisualElementContextData = z$1.object({
22
+ key: z$1.string(),
23
+ collection: z$1.string(),
24
+ item: z$1.union([z$1.string(), z$1.number()]),
25
+ fields: z$1.array(z$1.string()).optional(),
26
+ rect: z$1.object({
27
+ top: z$1.number(),
28
+ left: z$1.number(),
29
+ width: z$1.number(),
30
+ height: z$1.number()
31
31
  }).optional()
32
32
  });
33
- const PromptContextData = z.object({
34
- text: z.string(),
35
- prompt: z.record(z.string(), z.unknown()),
36
- values: z.record(z.string(), z.string())
33
+ const PromptContextData = z$1.object({
34
+ text: z$1.string(),
35
+ prompt: z$1.record(z$1.string(), z$1.unknown()),
36
+ values: z$1.record(z$1.string(), z$1.string())
37
37
  });
38
- const ContextAttachment = z.discriminatedUnion("type", [
39
- z.object({
40
- type: z.literal("item"),
41
- display: z.string(),
38
+ const ContextAttachment = z$1.discriminatedUnion("type", [
39
+ z$1.object({
40
+ type: z$1.literal("item"),
41
+ display: z$1.string(),
42
42
  data: ItemContextData,
43
- snapshot: z.record(z.string(), z.unknown())
43
+ snapshot: z$1.record(z$1.string(), z$1.unknown())
44
44
  }),
45
- z.object({
46
- type: z.literal("visual-element"),
47
- display: z.string(),
45
+ z$1.object({
46
+ type: z$1.literal("visual-element"),
47
+ display: z$1.string(),
48
48
  data: VisualElementContextData,
49
- snapshot: z.record(z.string(), z.unknown())
49
+ snapshot: z$1.record(z$1.string(), z$1.unknown())
50
50
  }),
51
- z.object({
52
- type: z.literal("prompt"),
53
- display: z.string(),
51
+ z$1.object({
52
+ type: z$1.literal("prompt"),
53
+ display: z$1.string(),
54
54
  data: PromptContextData,
55
- snapshot: z.record(z.string(), z.unknown())
55
+ snapshot: z$1.record(z$1.string(), z$1.unknown())
56
56
  })
57
57
  ]);
58
- const PageContext = z.object({
59
- path: z.string(),
60
- collection: z.string().optional(),
61
- item: z.union([z.string(), z.number()]).optional(),
62
- module: z.string().optional()
58
+ const PageContext = z$1.object({
59
+ path: z$1.string(),
60
+ collection: z$1.string().optional(),
61
+ item: z$1.union([z$1.string(), z$1.number()]).optional(),
62
+ module: z$1.string().optional()
63
63
  });
64
- const ChatContext = z.object({
65
- attachments: z.array(ContextAttachment).max(10).optional(),
64
+ const ChatContext = z$1.object({
65
+ attachments: z$1.array(ContextAttachment).max(10).optional(),
66
66
  page: PageContext.optional()
67
67
  });
68
- const ChatRequest = z.intersection(z.discriminatedUnion("provider", [
68
+ const ChatRequest = z$1.intersection(z$1.discriminatedUnion("provider", [
69
69
  ProviderOpenAi,
70
70
  ProviderAnthropic,
71
71
  ProviderGoogle,
72
72
  ProviderOpenAiCompatible
73
- ]), z.object({
74
- tools: z.array(ChatRequestTool),
75
- messages: z.array(z.looseObject({})),
76
- toolApprovals: z.record(z.string(), ToolApprovalMode).optional(),
73
+ ]), z$1.object({
74
+ tools: z$1.array(ChatRequestTool),
75
+ messages: z$1.array(z$1.looseObject({})),
76
+ toolApprovals: z$1.record(z$1.string(), ToolApprovalMode).optional(),
77
77
  context: ChatContext.optional()
78
78
  }));
79
79
 
@@ -1,18 +1,18 @@
1
1
  import { zodJsonSchema7Parser } from "../utils/zod-jsonschema7-parser.js";
2
2
  import { ProviderAnthropic, ProviderGoogle, ProviderOpenAi, ProviderOpenAiCompatible } from "./providers.js";
3
3
  import "ai";
4
- import { z } from "zod";
4
+ import { z as z$1 } from "zod";
5
5
 
6
6
  //#region src/ai/chat/models/object-request.ts
7
- const ObjectRequest = z.intersection(z.discriminatedUnion("provider", [
7
+ const ObjectRequest = z$1.intersection(z$1.discriminatedUnion("provider", [
8
8
  ProviderOpenAi,
9
9
  ProviderAnthropic,
10
10
  ProviderGoogle,
11
11
  ProviderOpenAiCompatible
12
- ]), z.object({
13
- prompt: z.string(),
14
- outputSchema: z.custom(zodJsonSchema7Parser, { message: "Invalid JSON schema" }),
15
- maxOutputTokens: z.number().int().min(256).optional()
12
+ ]), z$1.object({
13
+ prompt: z$1.string(),
14
+ outputSchema: z$1.custom(zodJsonSchema7Parser, { message: "Invalid JSON schema" }),
15
+ maxOutputTokens: z$1.number().int().min(256).optional()
16
16
  }));
17
17
 
18
18
  //#endregion
@@ -1,27 +1,27 @@
1
- import { z } from "zod";
1
+ import { z as z$1 } from "zod";
2
2
 
3
3
  //#region src/ai/chat/models/providers.ts
4
- const ProviderTypeSchema = z.enum([
4
+ const ProviderTypeSchema = z$1.enum([
5
5
  "openai",
6
6
  "anthropic",
7
7
  "google",
8
8
  "openai-compatible"
9
9
  ]);
10
- const ProviderOpenAi = z.object({
11
- provider: z.literal("openai"),
12
- model: z.string()
10
+ const ProviderOpenAi = z$1.object({
11
+ provider: z$1.literal("openai"),
12
+ model: z$1.string()
13
13
  });
14
- const ProviderAnthropic = z.object({
15
- provider: z.literal("anthropic"),
16
- model: z.string()
14
+ const ProviderAnthropic = z$1.object({
15
+ provider: z$1.literal("anthropic"),
16
+ model: z$1.string()
17
17
  });
18
- const ProviderGoogle = z.object({
19
- provider: z.literal("google"),
20
- model: z.string()
18
+ const ProviderGoogle = z$1.object({
19
+ provider: z$1.literal("google"),
20
+ model: z$1.string()
21
21
  });
22
- const ProviderOpenAiCompatible = z.object({
23
- provider: z.literal("openai-compatible"),
24
- model: z.string()
22
+ const ProviderOpenAiCompatible = z$1.object({
23
+ provider: z$1.literal("openai-compatible"),
24
+ model: z$1.string()
25
25
  });
26
26
 
27
27
  //#endregion
@@ -1,8 +1,8 @@
1
1
  import { fromZodError } from "zod-validation-error";
2
- import { z } from "zod";
2
+ import { z as z$1 } from "zod";
3
3
 
4
4
  //#region src/ai/chat/utils/parse-json-schema-7.ts
5
- const jsonType = z.enum([
5
+ const jsonType = z$1.enum([
6
6
  "null",
7
7
  "boolean",
8
8
  "object",
@@ -11,7 +11,7 @@ const jsonType = z.enum([
11
11
  "integer",
12
12
  "string"
13
13
  ]);
14
- const maybeDraft7Url = z.url({
14
+ const maybeDraft7Url = z$1.url({
15
15
  protocol: /^https?$/,
16
16
  hostname: /^json-schema\.org$/
17
17
  }).refine((val) => {
@@ -22,26 +22,26 @@ const maybeDraft7Url = z.url({
22
22
  return false;
23
23
  }
24
24
  }, { message: "Must be the JSON Schema Draft-07 meta-schema URL" });
25
- const JsonSchema7 = z.object({
25
+ const JsonSchema7 = z$1.object({
26
26
  $schema: maybeDraft7Url.optional(),
27
- $id: z.string().optional(),
28
- $ref: z.string().optional(),
29
- title: z.string().optional(),
30
- description: z.string().optional(),
31
- type: z.union([jsonType, z.array(jsonType).nonempty()]).optional(),
32
- properties: z.record(z.string(), z.any()).optional(),
33
- required: z.array(z.string()).optional(),
34
- items: z.union([z.any(), z.array(z.any()).nonempty()]).optional(),
35
- additionalProperties: z.union([z.boolean(), z.any()]).optional(),
36
- patternProperties: z.record(z.string(), z.any()).optional(),
37
- enum: z.array(z.any()).optional(),
38
- const: z.any().optional(),
39
- anyOf: z.array(z.any()).optional(),
40
- allOf: z.array(z.any()).optional(),
41
- oneOf: z.array(z.any()).optional(),
42
- not: z.any().optional(),
43
- definitions: z.record(z.string(), z.any()).optional(),
44
- $defs: z.record(z.string(), z.any()).optional()
27
+ $id: z$1.string().optional(),
28
+ $ref: z$1.string().optional(),
29
+ title: z$1.string().optional(),
30
+ description: z$1.string().optional(),
31
+ type: z$1.union([jsonType, z$1.array(jsonType).nonempty()]).optional(),
32
+ properties: z$1.record(z$1.string(), z$1.any()).optional(),
33
+ required: z$1.array(z$1.string()).optional(),
34
+ items: z$1.union([z$1.any(), z$1.array(z$1.any()).nonempty()]).optional(),
35
+ additionalProperties: z$1.union([z$1.boolean(), z$1.any()]).optional(),
36
+ patternProperties: z$1.record(z$1.string(), z$1.any()).optional(),
37
+ enum: z$1.array(z$1.any()).optional(),
38
+ const: z$1.any().optional(),
39
+ anyOf: z$1.array(z$1.any()).optional(),
40
+ allOf: z$1.array(z$1.any()).optional(),
41
+ oneOf: z$1.array(z$1.any()).optional(),
42
+ not: z$1.any().optional(),
43
+ definitions: z$1.record(z$1.string(), z$1.any()).optional(),
44
+ $defs: z$1.record(z$1.string(), z$1.any()).optional()
45
45
  }).refine((obj) => {
46
46
  const keys = new Set(Object.keys(obj));
47
47
  return [
@@ -1,14 +1,15 @@
1
1
  import { ItemsService } from "../../services/items.js";
2
- import { coerceJsonFields } from "../tools/utils.js";
3
2
  import { Url } from "../../utils/url.js";
4
3
  import "../../services/index.js";
4
+ import { coerceJsonFields } from "../tools/utils.js";
5
5
  import { findMcpTool, getAllMcpTools } from "../tools/index.js";
6
6
  import { DirectusTransport } from "./transport.js";
7
+ import { MCP_ACCESS_SCOPE, buildMcpWWWAuthenticateHeader, getMcpUrls } from "./utils.js";
7
8
  import { useEnv } from "@directus/env";
8
9
  import { ForbiddenError, InvalidPayloadError, isDirectusError } from "@directus/errors";
9
10
  import { isObject, toArray } from "@directus/utils";
10
11
  import { fromZodError } from "zod-validation-error";
11
- import { z } from "zod";
12
+ import { z as z$1 } from "zod";
12
13
  import { render, tokenize } from "micromustache";
13
14
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
14
15
  import { CallToolRequestSchema, ErrorCode as ErrorCode$1, GetPromptRequestSchema, InitializedNotificationSchema, JSONRPCMessageSchema, ListPromptsRequestSchema, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
@@ -34,11 +35,48 @@ var DirectusMCP = class {
34
35
  } });
35
36
  }
36
37
  /**
37
- * This handleRequest function is not awaiting lower level logic resulting in the actual
38
- * response being an asynchronous side effect happening after the function has returned
38
+ * Send a 401 with WWW-Authenticate per RFC 6750 / RFC 9728.
39
+ * Includes `resource_metadata` pointing to `/.well-known/oauth-protected-resource/mcp`
40
+ * so clients can discover the authorization server from a 401 response.
41
+ */
42
+ sendUnauthorized(res, error, status = 401) {
43
+ const { metadataUrl } = getMcpUrls();
44
+ res.set("WWW-Authenticate", buildMcpWWWAuthenticateHeader(metadataUrl, error)).set("Access-Control-Expose-Headers", "WWW-Authenticate").status(status).send();
45
+ }
46
+ /**
47
+ * Handle an incoming MCP JSON-RPC request.
48
+ *
49
+ * OAuth-specific checks (when `accountability.oauth` is set):
50
+ * - Transport restriction: token must be in Authorization header (RFC 6750), not cookie/query
51
+ * - Scope check: must include mcp:access
52
+ * - Audience check: must match the canonical MCP resource URL (PUBLIC_URL/mcp)
53
+ *
54
+ * Note: this function does not await lower-level logic; the actual response is an
55
+ * asynchronous side effect happening after the function returns.
56
+ *
57
+ * @see sendUnauthorized for WWW-Authenticate format (RFC 9728 `resource_metadata` attribute)
39
58
  */
40
59
  handleRequest(req, res) {
41
- if (!req.accountability?.user && !req.accountability?.role && req.accountability?.admin !== true) throw new ForbiddenError();
60
+ const oauth = req.accountability?.oauth;
61
+ if (!req.accountability?.user && !req.accountability?.role && req.accountability?.admin !== true) {
62
+ this.sendUnauthorized(res);
63
+ return;
64
+ }
65
+ if (oauth) {
66
+ if (req.tokenSource !== "header") {
67
+ this.sendUnauthorized(res, "invalid_request");
68
+ return;
69
+ }
70
+ if (!oauth.scopes.includes(MCP_ACCESS_SCOPE)) {
71
+ this.sendUnauthorized(res, "insufficient_scope", 403);
72
+ return;
73
+ }
74
+ const { resourceUrl } = getMcpUrls();
75
+ if (!oauth.aud.includes(resourceUrl)) {
76
+ this.sendUnauthorized(res, "invalid_token");
77
+ return;
78
+ }
79
+ }
42
80
  if (!req.accepts("application/json")) {
43
81
  res.status(405).send();
44
82
  return;
@@ -130,7 +168,7 @@ var DirectusMCP = class {
130
168
  tools.push({
131
169
  name: tool.name,
132
170
  description: tool.description,
133
- inputSchema: z.toJSONSchema(tool.inputSchema),
171
+ inputSchema: z$1.toJSONSchema(tool.inputSchema),
134
172
  annotations: tool.annotations
135
173
  });
136
174
  }
@@ -0,0 +1,31 @@
1
+ import { useEnv } from "@directus/env";
2
+
3
+ //#region src/ai/mcp/utils.ts
4
+ /** The only OAuth scope for MCP access. Used in JWT claims, discovery metadata, and scope validation. */
5
+ const MCP_ACCESS_SCOPE = "mcp:access";
6
+ /**
7
+ * Canonical MCP resource and discovery URLs derived from PUBLIC_URL.
8
+ */
9
+ function getMcpUrls() {
10
+ const base = useEnv()["PUBLIC_URL"].replace(/\/+$/, "");
11
+ return {
12
+ issuerUrl: base || "/",
13
+ resourceUrl: `${base}/mcp`,
14
+ metadataUrl: `${base}/.well-known/oauth-protected-resource/mcp`
15
+ };
16
+ }
17
+ /** Check if a request path targets the concrete MCP endpoint. */
18
+ function isMcpPath(path) {
19
+ return path === "/mcp" || path === "/mcp/";
20
+ }
21
+ /**
22
+ * RFC 6750 / RFC 9728 WWW-Authenticate header for MCP OAuth responses.
23
+ */
24
+ function buildMcpWWWAuthenticateHeader(metadataUrl, error) {
25
+ let header = `Bearer resource_metadata="${metadataUrl}", scope="${MCP_ACCESS_SCOPE}"`;
26
+ if (error) header += `, error="${error}"`;
27
+ return header;
28
+ }
29
+
30
+ //#endregion
31
+ export { MCP_ACCESS_SCOPE, buildMcpWWWAuthenticateHeader, getMcpUrls, isMcpPath };
@@ -4,13 +4,13 @@ import { requireText } from "../../../utils/require-text.js";
4
4
  import { defineTool } from "../define-tool.js";
5
5
  import { UnsupportedMediaTypeError } from "@directus/errors";
6
6
  import { fileURLToPath } from "node:url";
7
- import { z } from "zod";
7
+ import { z as z$1 } from "zod";
8
8
  import { dirname, resolve } from "node:path";
9
9
 
10
10
  //#region src/ai/tools/assets/index.ts
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
- const AssetsValidateSchema = z.strictObject({ id: z.string() });
13
- const AssetsInputSchema = z.object({ id: z.string() });
12
+ const AssetsValidateSchema = z$1.strictObject({ id: z$1.string() });
13
+ const AssetsInputSchema = z$1.object({ id: z$1.string() });
14
14
  const assets = defineTool({
15
15
  name: "assets",
16
16
  description: requireText(resolve(__dirname, "./prompt.md")),
@@ -5,38 +5,38 @@ import { CollectionItemInputSchema, CollectionItemValidateCreateSchema, Collecti
5
5
  import { InvalidPayloadError } from "@directus/errors";
6
6
  import { isObject, toArray } from "@directus/utils";
7
7
  import { fileURLToPath } from "node:url";
8
- import { z } from "zod";
8
+ import { z as z$1 } from "zod";
9
9
  import { dirname, resolve } from "node:path";
10
10
 
11
11
  //#region src/ai/tools/collections/index.ts
12
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
- const CollectionsValidateSchema = z.discriminatedUnion("action", [
14
- z.strictObject({
15
- action: z.literal("create"),
16
- data: z.array(CollectionItemValidateCreateSchema)
13
+ const CollectionsValidateSchema = z$1.discriminatedUnion("action", [
14
+ z$1.strictObject({
15
+ action: z$1.literal("create"),
16
+ data: z$1.array(CollectionItemValidateCreateSchema)
17
17
  }),
18
- z.strictObject({
19
- action: z.literal("read"),
20
- keys: z.array(z.string()).optional()
18
+ z$1.strictObject({
19
+ action: z$1.literal("read"),
20
+ keys: z$1.array(z$1.string()).optional()
21
21
  }),
22
- z.strictObject({
23
- action: z.literal("update"),
24
- data: z.array(CollectionItemValidateUpdateSchema)
22
+ z$1.strictObject({
23
+ action: z$1.literal("update"),
24
+ data: z$1.array(CollectionItemValidateUpdateSchema)
25
25
  }),
26
- z.strictObject({
27
- action: z.literal("delete"),
28
- keys: z.array(z.string())
26
+ z$1.strictObject({
27
+ action: z$1.literal("delete"),
28
+ keys: z$1.array(z$1.string())
29
29
  })
30
30
  ]);
31
- const CollectionsInputSchema = z.object({
32
- action: z.enum([
31
+ const CollectionsInputSchema = z$1.object({
32
+ action: z$1.enum([
33
33
  "create",
34
34
  "read",
35
35
  "update",
36
36
  "delete"
37
37
  ]).describe("The operation to perform"),
38
- keys: z.array(z.string()).optional(),
39
- data: z.array(CollectionItemInputSchema).optional()
38
+ keys: z$1.array(z$1.string()).optional(),
39
+ data: z$1.array(CollectionItemInputSchema).optional()
40
40
  });
41
41
  const collections = defineTool({
42
42
  name: "collections",
@@ -10,41 +10,41 @@ import { FieldItemInputSchema, FieldItemValidateSchema, RawFieldItemInputSchema,
10
10
  import { InvalidPayloadError } from "@directus/errors";
11
11
  import { toArray } from "@directus/utils";
12
12
  import { fileURLToPath } from "node:url";
13
- import { z } from "zod";
13
+ import { z as z$1 } from "zod";
14
14
  import { dirname, resolve } from "node:path";
15
15
 
16
16
  //#region src/ai/tools/fields/index.ts
17
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
- const FieldsBaseValidateSchema = z.strictObject({ collection: z.string() });
19
- const FieldsValidateSchema = z.discriminatedUnion("action", [
18
+ const FieldsBaseValidateSchema = z$1.strictObject({ collection: z$1.string() });
19
+ const FieldsValidateSchema = z$1.discriminatedUnion("action", [
20
20
  FieldsBaseValidateSchema.extend({
21
- action: z.literal("create"),
22
- data: z.union([z.array(FieldItemValidateSchema), FieldItemValidateSchema])
21
+ action: z$1.literal("create"),
22
+ data: z$1.union([z$1.array(FieldItemValidateSchema), FieldItemValidateSchema])
23
23
  }),
24
- z.object({
25
- action: z.literal("read"),
26
- collection: z.string().optional(),
27
- field: z.string().optional()
24
+ z$1.object({
25
+ action: z$1.literal("read"),
26
+ collection: z$1.string().optional(),
27
+ field: z$1.string().optional()
28
28
  }),
29
29
  FieldsBaseValidateSchema.extend({
30
- action: z.literal("update"),
31
- data: z.array(RawFieldItemValidateSchema.partial({ type: true }))
30
+ action: z$1.literal("update"),
31
+ data: z$1.array(RawFieldItemValidateSchema.partial({ type: true }))
32
32
  }),
33
33
  FieldsBaseValidateSchema.extend({
34
- action: z.literal("delete"),
35
- field: z.string()
34
+ action: z$1.literal("delete"),
35
+ field: z$1.string()
36
36
  })
37
37
  ]);
38
- const FieldsInputSchema = z.object({
39
- action: z.enum([
38
+ const FieldsInputSchema = z$1.object({
39
+ action: z$1.enum([
40
40
  "read",
41
41
  "create",
42
42
  "update",
43
43
  "delete"
44
44
  ]).describe("The operation to perform"),
45
- collection: z.string().describe("The name of the collection").optional(),
46
- field: z.string().describe("The name of the field. Required for delete. Optional for read (omit to read all fields). Do not use for create or update.").optional(),
47
- data: z.array(FieldItemInputSchema.extend({ children: RawFieldItemInputSchema.shape.children }).partial().required({ field: true })).describe("Array of field objects for create/update actions. Each object must include \"field\" (the field name).").optional()
45
+ collection: z$1.string().describe("The name of the collection").optional(),
46
+ field: z$1.string().describe("The name of the field. Required for delete. Optional for read (omit to read all fields). Do not use for create or update.").optional(),
47
+ data: z$1.array(FieldItemInputSchema.extend({ children: RawFieldItemInputSchema.shape.children }).partial().required({ field: true })).describe("Array of field objects for create/update actions. Each object must include \"field\" (the field name).").optional()
48
48
  });
49
49
  const fields = defineTool({
50
50
  name: "fields",
@@ -5,42 +5,42 @@ import { FileImportItemInputSchema, FileImportItemValidateSchema, FileItemInputS
5
5
  import { buildSanitizedQueryFromArgs } from "../utils.js";
6
6
  import { isObject } from "@directus/utils";
7
7
  import { fileURLToPath } from "node:url";
8
- import { z } from "zod";
8
+ import { z as z$1 } from "zod";
9
9
  import { dirname, resolve } from "node:path";
10
10
 
11
11
  //#region src/ai/tools/files/index.ts
12
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
- const FilesValidateSchema = z.discriminatedUnion("action", [
14
- z.strictObject({
15
- action: z.literal("read"),
16
- keys: z.array(PrimaryKeyValidateSchema).optional(),
13
+ const FilesValidateSchema = z$1.discriminatedUnion("action", [
14
+ z$1.strictObject({
15
+ action: z$1.literal("read"),
16
+ keys: z$1.array(PrimaryKeyValidateSchema).optional(),
17
17
  query: QueryValidateSchema.optional()
18
18
  }),
19
- z.strictObject({
20
- action: z.literal("update"),
19
+ z$1.strictObject({
20
+ action: z$1.literal("update"),
21
21
  data: FileItemValidateSchema,
22
- keys: z.array(PrimaryKeyValidateSchema).optional(),
22
+ keys: z$1.array(PrimaryKeyValidateSchema).optional(),
23
23
  query: QueryValidateSchema.optional()
24
24
  }),
25
- z.strictObject({
26
- action: z.literal("delete"),
27
- keys: z.array(PrimaryKeyValidateSchema)
25
+ z$1.strictObject({
26
+ action: z$1.literal("delete"),
27
+ keys: z$1.array(PrimaryKeyValidateSchema)
28
28
  }),
29
- z.strictObject({
30
- action: z.literal("import"),
31
- data: z.array(FileImportItemValidateSchema)
29
+ z$1.strictObject({
30
+ action: z$1.literal("import"),
31
+ data: z$1.array(FileImportItemValidateSchema)
32
32
  })
33
33
  ]);
34
- const FilesInputSchema = z.object({
35
- action: z.enum([
34
+ const FilesInputSchema = z$1.object({
35
+ action: z$1.enum([
36
36
  "read",
37
37
  "update",
38
38
  "delete",
39
39
  "import"
40
40
  ]).describe("The operation to perform"),
41
41
  query: QueryInputSchema.optional(),
42
- keys: z.array(PrimaryKeyInputSchema).optional(),
43
- data: z.array(FileItemInputSchema.extend({ ...FileImportItemInputSchema.shape }).partial()).optional()
42
+ keys: z$1.array(PrimaryKeyInputSchema).optional(),
43
+ data: z$1.array(FileItemInputSchema.extend({ ...FileImportItemInputSchema.shape }).partial()).optional()
44
44
  });
45
45
  const files = defineTool({
46
46
  name: "files",
@@ -1,37 +1,37 @@
1
1
  import { requireText } from "../../../utils/require-text.js";
2
2
  import { defineTool } from "../define-tool.js";
3
+ import { FlowsService } from "../../../services/flows.js";
3
4
  import { FlowItemInputSchema, FlowItemValidateSchema, QueryInputSchema, QueryValidateSchema } from "../schema.js";
4
5
  import { buildSanitizedQueryFromArgs } from "../utils.js";
5
- import { FlowsService } from "../../../services/flows.js";
6
6
  import { isObject } from "@directus/utils";
7
7
  import { fileURLToPath } from "node:url";
8
- import { z } from "zod";
8
+ import { z as z$1 } from "zod";
9
9
  import { dirname, resolve } from "node:path";
10
10
 
11
11
  //#region src/ai/tools/flows/index.ts
12
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
- const FlowsValidateSchema = z.discriminatedUnion("action", [
14
- z.strictObject({
15
- action: z.literal("create"),
13
+ const FlowsValidateSchema = z$1.discriminatedUnion("action", [
14
+ z$1.strictObject({
15
+ action: z$1.literal("create"),
16
16
  data: FlowItemValidateSchema
17
17
  }),
18
- z.strictObject({
19
- action: z.literal("read"),
18
+ z$1.strictObject({
19
+ action: z$1.literal("read"),
20
20
  query: QueryValidateSchema.optional()
21
21
  }),
22
- z.strictObject({
23
- action: z.literal("update"),
24
- key: z.string(),
22
+ z$1.strictObject({
23
+ action: z$1.literal("update"),
24
+ key: z$1.string(),
25
25
  data: FlowItemValidateSchema,
26
26
  query: QueryValidateSchema.optional()
27
27
  }),
28
- z.strictObject({
29
- action: z.literal("delete"),
30
- key: z.string()
28
+ z$1.strictObject({
29
+ action: z$1.literal("delete"),
30
+ key: z$1.string()
31
31
  })
32
32
  ]);
33
- const FlowsInputSchema = z.object({
34
- action: z.enum([
33
+ const FlowsInputSchema = z$1.object({
34
+ action: z$1.enum([
35
35
  "create",
36
36
  "read",
37
37
  "update",
@@ -39,7 +39,7 @@ const FlowsInputSchema = z.object({
39
39
  ]).describe("The operation to perform"),
40
40
  query: QueryInputSchema.optional(),
41
41
  data: FlowItemInputSchema.optional(),
42
- key: z.string().optional()
42
+ key: z$1.string().optional()
43
43
  });
44
44
  const flows = defineTool({
45
45
  name: "flows",