@directus/api 32.1.0 → 32.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/chat/constants/system-prompt.d.ts +1 -0
- package/dist/ai/chat/constants/system-prompt.js +51 -0
- package/dist/ai/chat/controllers/chat.post.d.ts +2 -0
- package/dist/ai/chat/controllers/chat.post.js +47 -0
- package/dist/ai/chat/lib/create-ui-stream.d.ts +15 -0
- package/dist/ai/chat/lib/create-ui-stream.js +42 -0
- package/dist/ai/chat/middleware/load-settings.d.ts +2 -0
- package/dist/ai/chat/middleware/load-settings.js +18 -0
- package/dist/ai/chat/models/chat-request.d.ts +34 -0
- package/dist/ai/chat/models/chat-request.js +26 -0
- package/dist/ai/chat/models/providers.d.ts +9 -0
- package/dist/ai/chat/models/providers.js +9 -0
- package/dist/ai/chat/router.d.ts +1 -0
- package/dist/ai/chat/router.js +5 -0
- package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.d.ts +9 -0
- package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.js +38 -0
- package/dist/ai/chat/utils/fix-error-tool-calls.d.ts +12 -0
- package/dist/ai/chat/utils/fix-error-tool-calls.js +30 -0
- package/dist/ai/chat/utils/parse-json-schema-7.d.ts +13 -0
- package/dist/ai/chat/utils/parse-json-schema-7.js +75 -0
- package/dist/{mcp → ai/mcp}/server.d.ts +13 -16
- package/dist/{mcp → ai/mcp}/server.js +4 -13
- package/dist/ai/mcp/types.d.ts +15 -0
- package/dist/{mcp/tools/assets.js → ai/tools/assets/index.js} +8 -5
- package/dist/{mcp/tools/collections.js → ai/tools/collections/index.js} +7 -4
- package/dist/{mcp/tools/fields.js → ai/tools/fields/index.js} +12 -9
- package/dist/{mcp/tools/files.js → ai/tools/files/index.js} +11 -5
- package/dist/{mcp/tools/flows.js → ai/tools/flows/index.js} +11 -5
- package/dist/{mcp/tools/folders.js → ai/tools/folders/index.js} +12 -5
- package/dist/ai/tools/index.d.ts +15 -0
- package/dist/ai/tools/index.js +29 -0
- package/dist/{mcp/tools/items.js → ai/tools/items/index.js} +13 -6
- package/dist/{mcp/tools/prompts/items.md → ai/tools/items/prompt.md} +19 -15
- package/dist/{mcp/tools/operations.d.ts → ai/tools/operations/index.d.ts} +46 -0
- package/dist/{mcp/tools/operations.js → ai/tools/operations/index.js} +12 -5
- package/dist/{mcp/tools/relations.js → ai/tools/relations/index.js} +7 -4
- package/dist/{mcp/tools/schema.d.ts → ai/tools/schema/index.d.ts} +1 -1
- package/dist/{mcp/tools/schema.js → ai/tools/schema/index.js} +9 -6
- package/dist/{mcp/tools/system.js → ai/tools/system/index.js} +7 -4
- package/dist/{mcp/tools/trigger-flow.js → ai/tools/trigger-flow/index.js} +8 -5
- package/dist/{mcp → ai/tools}/types.d.ts +1 -17
- package/dist/ai/tools/utils.d.ts +9 -0
- package/dist/ai/tools/utils.js +17 -0
- package/dist/app.js +5 -0
- package/dist/auth/drivers/oauth2.d.ts +2 -1
- package/dist/auth/drivers/oauth2.js +17 -22
- package/dist/auth/drivers/openid.d.ts +2 -1
- package/dist/auth/drivers/openid.js +13 -18
- package/dist/auth/drivers/saml.js +6 -3
- package/dist/controllers/assets.js +39 -2
- package/dist/controllers/mcp.js +1 -1
- package/dist/database/migrations/20240806A-permissions-policies.js +2 -2
- package/dist/database/migrations/20251103A-add-ai-settings.d.ts +3 -0
- package/dist/database/migrations/20251103A-add-ai-settings.js +14 -0
- package/dist/database/run-ast/run-ast.js +1 -1
- package/dist/extensions/lib/installation/manager.js +5 -9
- package/dist/extensions/lib/sync/status.d.ts +11 -0
- package/dist/extensions/lib/sync/status.js +34 -0
- package/dist/extensions/lib/sync/sync.d.ts +6 -0
- package/dist/extensions/lib/sync/sync.js +90 -0
- package/dist/extensions/lib/sync/tracker.d.ts +18 -0
- package/dist/extensions/lib/sync/tracker.js +71 -0
- package/dist/extensions/lib/sync/utils.d.ts +24 -0
- package/dist/extensions/lib/sync/utils.js +62 -0
- package/dist/extensions/manager.d.ts +8 -4
- package/dist/extensions/manager.js +30 -13
- package/dist/middleware/respond.js +2 -2
- package/dist/permissions/lib/fetch-policies.d.ts +1 -1
- package/dist/permissions/lib/fetch-roles-tree.d.ts +6 -3
- package/dist/permissions/lib/fetch-roles-tree.js +5 -27
- package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +9 -7
- package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +17 -9
- package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +1 -1
- package/dist/permissions/utils/fetch-raw-permissions.d.ts +1 -1
- package/dist/permissions/utils/fetch-share-info.d.ts +1 -1
- package/dist/permissions/utils/fetch-share-info.js +1 -1
- package/dist/permissions/utils/filter-policies-by-ip.js +1 -1
- package/dist/permissions/utils/get-permissions-for-share.js +8 -8
- package/dist/permissions/utils/with-cache.d.ts +8 -6
- package/dist/permissions/utils/with-cache.js +12 -10
- package/dist/request/is-denied-ip.js +2 -2
- package/dist/services/assets/name-deduper.d.ts +7 -0
- package/dist/services/assets/name-deduper.js +23 -0
- package/dist/services/assets.d.ts +15 -2
- package/dist/services/assets.js +98 -5
- package/dist/services/authentication.js +4 -4
- package/dist/services/comments.js +2 -2
- package/dist/services/extensions.js +4 -0
- package/dist/services/folders.d.ts +27 -2
- package/dist/services/folders.js +75 -0
- package/dist/services/graphql/resolvers/query.js +1 -1
- package/dist/services/import-export.d.ts +1 -1
- package/dist/services/import-export.js +4 -5
- package/dist/services/notifications.js +2 -2
- package/dist/services/payload.js +20 -0
- package/dist/services/roles.js +2 -2
- package/dist/services/tus/server.js +3 -3
- package/dist/telemetry/utils/get-settings.d.ts +15 -0
- package/dist/telemetry/utils/get-settings.js +25 -9
- package/dist/test-utils/README.md +95 -24
- package/dist/test-utils/cache.d.ts +2 -2
- package/dist/test-utils/cache.js +2 -2
- package/dist/test-utils/{fields-service.d.ts → services/fields-service.d.ts} +1 -1
- package/dist/test-utils/{fields-service.js → services/fields-service.js} +3 -2
- package/dist/test-utils/services/files-service.d.ts +28 -0
- package/dist/test-utils/services/files-service.js +34 -0
- package/dist/test-utils/services/folders-service.d.ts +28 -0
- package/dist/test-utils/services/folders-service.js +33 -0
- package/dist/utils/encrypt.d.ts +2 -0
- package/dist/utils/encrypt.js +64 -0
- package/dist/utils/get-accountability-for-role.js +2 -2
- package/dist/utils/get-accountability-for-token.js +4 -4
- package/dist/utils/get-cache-key.js +2 -2
- package/dist/utils/is-login-redirect-allowed.d.ts +4 -0
- package/dist/{auth/utils → utils}/is-login-redirect-allowed.js +8 -16
- package/dist/utils/require-text.d.ts +1 -0
- package/dist/utils/require-text.js +4 -0
- package/dist/utils/require-yaml.js +2 -2
- package/package.json +31 -25
- package/dist/auth/utils/generate-callback-url.d.ts +0 -8
- package/dist/auth/utils/generate-callback-url.js +0 -11
- package/dist/auth/utils/is-login-redirect-allowed.d.ts +0 -8
- package/dist/extensions/lib/sync-extensions.d.ts +0 -3
- package/dist/extensions/lib/sync-extensions.js +0 -70
- package/dist/extensions/lib/sync-status.d.ts +0 -10
- package/dist/extensions/lib/sync-status.js +0 -27
- package/dist/mcp/tools/index.d.ts +0 -15
- package/dist/mcp/tools/index.js +0 -29
- package/dist/mcp/tools/prompts/index.d.ts +0 -16
- package/dist/mcp/tools/prompts/index.js +0 -19
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +0 -5
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +0 -7
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +0 -5
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +0 -10
- package/dist/permissions/modules/fetch-global-access/types.d.ts +0 -4
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +0 -4
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +0 -27
- package/dist/utils/get-date-formatted.d.ts +0 -1
- package/dist/utils/get-date-formatted.js +0 -10
- package/dist/utils/ip-in-networks.d.ts +0 -6
- package/dist/utils/ip-in-networks.js +0 -13
- /package/dist/{mcp → ai/mcp}/index.d.ts +0 -0
- /package/dist/{mcp → ai/mcp}/index.js +0 -0
- /package/dist/{mcp → ai/mcp}/transport.d.ts +0 -0
- /package/dist/{mcp → ai/mcp}/transport.js +0 -0
- /package/dist/{mcp → ai/mcp}/types.js +0 -0
- /package/dist/{mcp/tools/assets.d.ts → ai/tools/assets/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/assets.md → ai/tools/assets/prompt.md} +0 -0
- /package/dist/{mcp/tools/collections.d.ts → ai/tools/collections/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/collections.md → ai/tools/collections/prompt.md} +0 -0
- /package/dist/{mcp/define.d.ts → ai/tools/define-tool.d.ts} +0 -0
- /package/dist/{mcp/define.js → ai/tools/define-tool.js} +0 -0
- /package/dist/{mcp/tools/fields.d.ts → ai/tools/fields/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/fields.md → ai/tools/fields/prompt.md} +0 -0
- /package/dist/{mcp/tools/files.d.ts → ai/tools/files/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/files.md → ai/tools/files/prompt.md} +0 -0
- /package/dist/{mcp/tools/flows.d.ts → ai/tools/flows/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/flows.md → ai/tools/flows/prompt.md} +0 -0
- /package/dist/{mcp/tools/folders.d.ts → ai/tools/folders/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/folders.md → ai/tools/folders/prompt.md} +0 -0
- /package/dist/{mcp/tools/items.d.ts → ai/tools/items/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/operations.md → ai/tools/operations/prompt.md} +0 -0
- /package/dist/{mcp/tools/relations.d.ts → ai/tools/relations/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/relations.md → ai/tools/relations/prompt.md} +0 -0
- /package/dist/{mcp/tools/prompts/schema.md → ai/tools/schema/prompt.md} +0 -0
- /package/dist/{mcp → ai/tools}/schema.d.ts +0 -0
- /package/dist/{mcp → ai/tools}/schema.js +0 -0
- /package/dist/{mcp/tools/system.d.ts → ai/tools/system/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/system-prompt-description.md → ai/tools/system/prompt-description.md} +0 -0
- /package/dist/{mcp/tools/prompts/system-prompt.md → ai/tools/system/prompt.md} +0 -0
- /package/dist/{mcp/tools/trigger-flow.d.ts → ai/tools/trigger-flow/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/trigger-flow.md → ai/tools/trigger-flow/prompt.md} +0 -0
- /package/dist/{permissions/modules/fetch-global-access → ai/tools}/types.js +0 -0
- /package/dist/test-utils/{items-service.d.ts → services/items-service.d.ts} +0 -0
- /package/dist/test-utils/{items-service.js → services/items-service.js} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SYSTEM_PROMPT = "\n<behavior_instructions>\nYou are **Directus Assistant**, a Directus CMS expert with access to a Directus instance through specialized tools\n\n## Communication Style\n\n- **Be concise**: Users prefer short, direct responses. One-line confirmations: \"Created collection 'products'\"\n- **Match the audience**: Technical for developers, plain language for content editors\n- **NEVER guess**: If not at least 99% about field values or user intent, ask for clarification\n\n## Tool Usage Patterns\n\n### Discovery First\n\n1. Understand the user's task and what they need to achieve.\n2. Discover schema if needed for task - **schema()** with no params \u2192 lightweight collection list or **schema({ keys: [\"products\", \"categories\"] })** \u2192 full field/relation details\n3. Use other tools as needed to achieve the user's task.\n\n### Content Items\n\n- Use `fields` whenever possible to fetch only the exact fields you need\n- Use `filter` and `limit` to control the number of fetched items unless you absolutely need larger datasets\n- When presenting repeated structured data with 4+ items, use markdown tables for better readability\n\n### Schema & Data Changes\n\n- **Confirm before modifying any schema**: Collections, fields, relations always need approval from the user.\n- **Check namespace conflicts**: Collection folders and regular collections share namespace. Collection folders are\n distinct from file folders.\n\n### Safety Rules\n\n- **Deletions require confirmation**: ALWAYS ask before deleting anything\n- **Warn on bulk operations**: Alert when affecting many items (\"This updates 500 items\")\n- **Avoid duplicates**: Never create duplicates if you can't modify existing items\n- **Use semantic HTML**: No classes, IDs, or inline styles in content fields (unless explicitly asked for by the user)\n- **Permission errors**: Report immediately, don't retry\n\n### Behavior Rules\n\n- Call tools immediately without explanatory text\n- Use parallel tool calls when possible\n- If you don't have access to a certain tool, ask the user to grant you access to the tool from the chat settings.\n- If there are unused tools in context but task is simple, suggest disabling unused tools (once per conversation)\n\n## Error Handling\n\n- Auto-retry once for clear errors (\"field X required\")\n- Stop after 2 failures, consult user\n- If tool unavailable, suggest enabling in chat settings\n</behavior_instructions>";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const SYSTEM_PROMPT = `
|
|
2
|
+
<behavior_instructions>
|
|
3
|
+
You are **Directus Assistant**, a Directus CMS expert with access to a Directus instance through specialized tools
|
|
4
|
+
|
|
5
|
+
## Communication Style
|
|
6
|
+
|
|
7
|
+
- **Be concise**: Users prefer short, direct responses. One-line confirmations: "Created collection 'products'"
|
|
8
|
+
- **Match the audience**: Technical for developers, plain language for content editors
|
|
9
|
+
- **NEVER guess**: If not at least 99% about field values or user intent, ask for clarification
|
|
10
|
+
|
|
11
|
+
## Tool Usage Patterns
|
|
12
|
+
|
|
13
|
+
### Discovery First
|
|
14
|
+
|
|
15
|
+
1. Understand the user's task and what they need to achieve.
|
|
16
|
+
2. Discover schema if needed for task - **schema()** with no params → lightweight collection list or **schema({ keys: ["products", "categories"] })** → full field/relation details
|
|
17
|
+
3. Use other tools as needed to achieve the user's task.
|
|
18
|
+
|
|
19
|
+
### Content Items
|
|
20
|
+
|
|
21
|
+
- Use \`fields\` whenever possible to fetch only the exact fields you need
|
|
22
|
+
- Use \`filter\` and \`limit\` to control the number of fetched items unless you absolutely need larger datasets
|
|
23
|
+
- When presenting repeated structured data with 4+ items, use markdown tables for better readability
|
|
24
|
+
|
|
25
|
+
### Schema & Data Changes
|
|
26
|
+
|
|
27
|
+
- **Confirm before modifying any schema**: Collections, fields, relations always need approval from the user.
|
|
28
|
+
- **Check namespace conflicts**: Collection folders and regular collections share namespace. Collection folders are
|
|
29
|
+
distinct from file folders.
|
|
30
|
+
|
|
31
|
+
### Safety Rules
|
|
32
|
+
|
|
33
|
+
- **Deletions require confirmation**: ALWAYS ask before deleting anything
|
|
34
|
+
- **Warn on bulk operations**: Alert when affecting many items ("This updates 500 items")
|
|
35
|
+
- **Avoid duplicates**: Never create duplicates if you can't modify existing items
|
|
36
|
+
- **Use semantic HTML**: No classes, IDs, or inline styles in content fields (unless explicitly asked for by the user)
|
|
37
|
+
- **Permission errors**: Report immediately, don't retry
|
|
38
|
+
|
|
39
|
+
### Behavior Rules
|
|
40
|
+
|
|
41
|
+
- Call tools immediately without explanatory text
|
|
42
|
+
- Use parallel tool calls when possible
|
|
43
|
+
- If you don't have access to a certain tool, ask the user to grant you access to the tool from the chat settings.
|
|
44
|
+
- If there are unused tools in context but task is simple, suggest disabling unused tools (once per conversation)
|
|
45
|
+
|
|
46
|
+
## Error Handling
|
|
47
|
+
|
|
48
|
+
- Auto-retry once for clear errors ("field X required")
|
|
49
|
+
- Stop after 2 failures, consult user
|
|
50
|
+
- If tool unavailable, suggest enabling in chat settings
|
|
51
|
+
</behavior_instructions>`;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
2
|
+
import { safeValidateUIMessages } from 'ai';
|
|
3
|
+
import { fromZodError } from 'zod-validation-error';
|
|
4
|
+
import { createUiStream } from '../lib/create-ui-stream.js';
|
|
5
|
+
import { ChatRequest } from '../models/chat-request.js';
|
|
6
|
+
import { chatRequestToolToAiSdkTool } from '../utils/chat-request-tool-to-ai-sdk-tool.js';
|
|
7
|
+
import { fixErrorToolCalls } from '../utils/fix-error-tool-calls.js';
|
|
8
|
+
export const aiChatPostHandler = async (req, res, _next) => {
|
|
9
|
+
if (!req.accountability?.app) {
|
|
10
|
+
throw new ForbiddenError();
|
|
11
|
+
}
|
|
12
|
+
const parseResult = ChatRequest.safeParse(req.body);
|
|
13
|
+
if (!parseResult.success) {
|
|
14
|
+
throw new InvalidPayloadError({ reason: fromZodError(parseResult.error).message });
|
|
15
|
+
}
|
|
16
|
+
const { provider, model, messages: rawMessages, tools: requestedTools, toolApprovals } = parseResult.data;
|
|
17
|
+
if (rawMessages.length === 0) {
|
|
18
|
+
throw new InvalidPayloadError({ reason: `"messages" must not be empty` });
|
|
19
|
+
}
|
|
20
|
+
const tools = requestedTools.reduce((acc, t) => {
|
|
21
|
+
const name = typeof t === 'string' ? t : t.name;
|
|
22
|
+
const aiTool = chatRequestToolToAiSdkTool({
|
|
23
|
+
chatRequestTool: t,
|
|
24
|
+
accountability: req.accountability,
|
|
25
|
+
schema: req.schema,
|
|
26
|
+
...(toolApprovals && { toolApprovals }),
|
|
27
|
+
});
|
|
28
|
+
acc[name] = aiTool;
|
|
29
|
+
return acc;
|
|
30
|
+
}, {});
|
|
31
|
+
const fixedMessages = fixErrorToolCalls(rawMessages);
|
|
32
|
+
const validationResult = await safeValidateUIMessages({ messages: fixedMessages });
|
|
33
|
+
if (validationResult.success === false) {
|
|
34
|
+
throw new InvalidPayloadError({ reason: validationResult.error.message });
|
|
35
|
+
}
|
|
36
|
+
const stream = createUiStream(validationResult.data, {
|
|
37
|
+
provider,
|
|
38
|
+
model,
|
|
39
|
+
tools: tools,
|
|
40
|
+
apiKeys: res.locals['ai'].apiKeys,
|
|
41
|
+
systemPrompt: res.locals['ai'].systemPrompt,
|
|
42
|
+
onUsage: (usage) => {
|
|
43
|
+
res.write(`data: ${JSON.stringify({ type: 'data-usage', data: usage })}\n\n`);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
stream.pipeUIMessageStreamToResponse(res);
|
|
47
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type LanguageModelUsage, type Tool, type UIMessage, type StreamTextResult } from 'ai';
|
|
2
|
+
export interface CreateUiStreamOptions {
|
|
3
|
+
provider: 'openai' | 'anthropic';
|
|
4
|
+
model: string;
|
|
5
|
+
tools: {
|
|
6
|
+
[x: string]: Tool;
|
|
7
|
+
};
|
|
8
|
+
apiKeys: {
|
|
9
|
+
openai: string | null;
|
|
10
|
+
anthropic: string | null;
|
|
11
|
+
};
|
|
12
|
+
systemPrompt?: string;
|
|
13
|
+
onUsage?: (usage: Pick<LanguageModelUsage, 'inputTokens' | 'outputTokens' | 'totalTokens'>) => void | Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export declare const createUiStream: (messages: UIMessage[], { provider, model, tools, apiKeys, systemPrompt, onUsage }: CreateUiStreamOptions) => StreamTextResult<Record<string, Tool<any, any>>, any>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
2
|
+
import { createOpenAI } from '@ai-sdk/openai';
|
|
3
|
+
import { ServiceUnavailableError } from '@directus/errors';
|
|
4
|
+
import { convertToModelMessages, stepCountIs, streamText, } from 'ai';
|
|
5
|
+
import { SYSTEM_PROMPT } from '../constants/system-prompt.js';
|
|
6
|
+
export const createUiStream = (messages, { provider, model, tools, apiKeys, systemPrompt, onUsage }) => {
|
|
7
|
+
if (apiKeys[provider] === null) {
|
|
8
|
+
throw new ServiceUnavailableError({ service: provider, reason: 'No API key configured for LLM provider' });
|
|
9
|
+
}
|
|
10
|
+
let modelProvider;
|
|
11
|
+
if (provider === 'openai') {
|
|
12
|
+
modelProvider = createOpenAI({ apiKey: apiKeys.openai });
|
|
13
|
+
}
|
|
14
|
+
else if (provider === 'anthropic') {
|
|
15
|
+
modelProvider = createAnthropic({ apiKey: apiKeys.anthropic });
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
throw new Error(`Unexpected provider given: "${provider}"`);
|
|
19
|
+
}
|
|
20
|
+
systemPrompt ||= SYSTEM_PROMPT;
|
|
21
|
+
const stream = streamText({
|
|
22
|
+
system: systemPrompt,
|
|
23
|
+
model: modelProvider(model),
|
|
24
|
+
messages: convertToModelMessages(messages),
|
|
25
|
+
stopWhen: [stepCountIs(10)],
|
|
26
|
+
providerOptions: {
|
|
27
|
+
openai: {
|
|
28
|
+
reasoningSummary: 'auto',
|
|
29
|
+
store: false,
|
|
30
|
+
include: ['reasoning.encrypted_content'],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
tools,
|
|
34
|
+
onFinish({ usage }) {
|
|
35
|
+
if (onUsage) {
|
|
36
|
+
const { inputTokens, outputTokens, totalTokens } = usage;
|
|
37
|
+
onUsage({ inputTokens, outputTokens, totalTokens });
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
return stream;
|
|
42
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SettingsService } from '../../../services/settings.js';
|
|
2
|
+
import { getSchema } from '../../../utils/get-schema.js';
|
|
3
|
+
export const loadSettings = async (_req, res, next) => {
|
|
4
|
+
const service = new SettingsService({
|
|
5
|
+
schema: await getSchema(),
|
|
6
|
+
});
|
|
7
|
+
const { ai_openai_api_key, ai_anthropic_api_key, ai_system_prompt } = await service.readSingleton({
|
|
8
|
+
fields: ['ai_openai_api_key', 'ai_anthropic_api_key', 'ai_system_prompt'],
|
|
9
|
+
});
|
|
10
|
+
res.locals['ai'] = {
|
|
11
|
+
apiKeys: {
|
|
12
|
+
openai: ai_openai_api_key,
|
|
13
|
+
anthropic: ai_anthropic_api_key,
|
|
14
|
+
},
|
|
15
|
+
systemPrompt: ai_system_prompt,
|
|
16
|
+
};
|
|
17
|
+
return next();
|
|
18
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type JSONSchema7 } from 'ai';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const ChatRequestTool: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
4
|
+
name: z.ZodString;
|
|
5
|
+
description: z.ZodString;
|
|
6
|
+
inputSchema: z.ZodCustom<JSONSchema7, JSONSchema7>;
|
|
7
|
+
}, z.core.$strip>]>;
|
|
8
|
+
export type ChatRequestTool = z.infer<typeof ChatRequestTool>;
|
|
9
|
+
export declare const ToolApprovalMode: z.ZodEnum<{
|
|
10
|
+
always: "always";
|
|
11
|
+
ask: "ask";
|
|
12
|
+
disabled: "disabled";
|
|
13
|
+
}>;
|
|
14
|
+
export type ToolApprovalMode = z.infer<typeof ToolApprovalMode>;
|
|
15
|
+
export declare const ChatRequest: z.ZodIntersection<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
16
|
+
provider: z.ZodLiteral<"openai">;
|
|
17
|
+
model: z.ZodUnion<readonly [z.ZodLiteral<"gpt-5">, z.ZodLiteral<"gpt-5-nano">, z.ZodLiteral<"gpt-5-mini">, z.ZodLiteral<"gpt-5-pro">]>;
|
|
18
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
19
|
+
provider: z.ZodLiteral<"anthropic">;
|
|
20
|
+
model: z.ZodUnion<readonly [z.ZodLiteral<"claude-sonnet-4-5">, z.ZodLiteral<"claude-haiku-4-5">, z.ZodLiteral<"claude-opus-4-1">]>;
|
|
21
|
+
}, z.core.$strip>], "provider">, z.ZodObject<{
|
|
22
|
+
tools: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
23
|
+
name: z.ZodString;
|
|
24
|
+
description: z.ZodString;
|
|
25
|
+
inputSchema: z.ZodCustom<JSONSchema7, JSONSchema7>;
|
|
26
|
+
}, z.core.$strip>]>>;
|
|
27
|
+
messages: z.ZodArray<z.ZodObject<{}, z.core.$loose>>;
|
|
28
|
+
toolApprovals: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
29
|
+
always: "always";
|
|
30
|
+
ask: "ask";
|
|
31
|
+
disabled: "disabled";
|
|
32
|
+
}>>>;
|
|
33
|
+
}, z.core.$strip>>;
|
|
34
|
+
export type ChatRequest = z.infer<typeof ChatRequest>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {} from 'ai';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { parseJsonSchema7 } from '../utils/parse-json-schema-7.js';
|
|
4
|
+
import { ProviderAnthropic, ProviderOpenAi } from './providers.js';
|
|
5
|
+
export const ChatRequestTool = z.union([
|
|
6
|
+
z.string(),
|
|
7
|
+
z.object({
|
|
8
|
+
name: z.string(),
|
|
9
|
+
description: z.string(),
|
|
10
|
+
inputSchema: z.custom((schema) => {
|
|
11
|
+
try {
|
|
12
|
+
parseJsonSchema7(schema);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}, { message: 'Invalid JSON schema' }),
|
|
19
|
+
}),
|
|
20
|
+
]);
|
|
21
|
+
export const ToolApprovalMode = z.enum(['always', 'ask', 'disabled']);
|
|
22
|
+
export const ChatRequest = z.intersection(z.discriminatedUnion('provider', [ProviderOpenAi, ProviderAnthropic]), z.object({
|
|
23
|
+
tools: z.array(ChatRequestTool),
|
|
24
|
+
messages: z.array(z.looseObject({})),
|
|
25
|
+
toolApprovals: z.record(z.string(), ToolApprovalMode).optional(),
|
|
26
|
+
}));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const ProviderOpenAi: z.ZodObject<{
|
|
3
|
+
provider: z.ZodLiteral<"openai">;
|
|
4
|
+
model: z.ZodUnion<readonly [z.ZodLiteral<"gpt-5">, z.ZodLiteral<"gpt-5-nano">, z.ZodLiteral<"gpt-5-mini">, z.ZodLiteral<"gpt-5-pro">]>;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
export declare const ProviderAnthropic: z.ZodObject<{
|
|
7
|
+
provider: z.ZodLiteral<"anthropic">;
|
|
8
|
+
model: z.ZodUnion<readonly [z.ZodLiteral<"claude-sonnet-4-5">, z.ZodLiteral<"claude-haiku-4-5">, z.ZodLiteral<"claude-opus-4-1">]>;
|
|
9
|
+
}, z.core.$strip>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const ProviderOpenAi = z.object({
|
|
3
|
+
provider: z.literal('openai'),
|
|
4
|
+
model: z.union([z.literal('gpt-5'), z.literal('gpt-5-nano'), z.literal('gpt-5-mini'), z.literal('gpt-5-pro')]),
|
|
5
|
+
});
|
|
6
|
+
export const ProviderAnthropic = z.object({
|
|
7
|
+
provider: z.literal('anthropic'),
|
|
8
|
+
model: z.union([z.literal('claude-sonnet-4-5'), z.literal('claude-haiku-4-5'), z.literal('claude-opus-4-1')]),
|
|
9
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const aiChatRouter: import("express-serve-static-core").Router;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import asyncHandler from '../../utils/async-handler.js';
|
|
3
|
+
import { aiChatPostHandler } from './controllers/chat.post.js';
|
|
4
|
+
import { loadSettings } from './middleware/load-settings.js';
|
|
5
|
+
export const aiChatRouter = Router().post('/', asyncHandler(loadSettings), asyncHandler(aiChatPostHandler));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Accountability, type SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { Tool } from 'ai';
|
|
3
|
+
import type { ChatRequestTool, ToolApprovalMode } from '../models/chat-request.js';
|
|
4
|
+
export declare const chatRequestToolToAiSdkTool: ({ chatRequestTool, accountability, schema, toolApprovals, }: {
|
|
5
|
+
chatRequestTool: ChatRequestTool;
|
|
6
|
+
accountability: Accountability;
|
|
7
|
+
schema: SchemaOverview;
|
|
8
|
+
toolApprovals?: Record<string, ToolApprovalMode>;
|
|
9
|
+
}) => Tool;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { InvalidPayloadError } from '@directus/errors';
|
|
2
|
+
import {} from '@directus/types';
|
|
3
|
+
import { jsonSchema, tool } from 'ai';
|
|
4
|
+
import { fromZodError } from 'zod-validation-error';
|
|
5
|
+
import { ALL_TOOLS } from '../../tools/index.js';
|
|
6
|
+
export const chatRequestToolToAiSdkTool = ({ chatRequestTool, accountability, schema, toolApprovals, }) => {
|
|
7
|
+
if (typeof chatRequestTool === 'string') {
|
|
8
|
+
const directusTool = ALL_TOOLS.find(({ name }) => name === chatRequestTool);
|
|
9
|
+
if (!directusTool) {
|
|
10
|
+
throw new InvalidPayloadError({ reason: `Tool by name "${chatRequestTool}" does not exist` });
|
|
11
|
+
}
|
|
12
|
+
// Determine if tool needs approval based on client settings
|
|
13
|
+
// Default to 'ask' (needs approval) if not specified
|
|
14
|
+
const approvalMode = toolApprovals?.[chatRequestTool] ?? 'ask';
|
|
15
|
+
const needsApproval = approvalMode !== 'always';
|
|
16
|
+
return tool({
|
|
17
|
+
name: directusTool.name,
|
|
18
|
+
description: directusTool.description,
|
|
19
|
+
inputSchema: directusTool.inputSchema,
|
|
20
|
+
needsApproval,
|
|
21
|
+
execute: async (rawArgs) => {
|
|
22
|
+
const { error, data: args } = directusTool.validateSchema?.safeParse(rawArgs) ?? {
|
|
23
|
+
data: rawArgs,
|
|
24
|
+
};
|
|
25
|
+
if (error) {
|
|
26
|
+
throw new InvalidPayloadError({ reason: fromZodError(error).message });
|
|
27
|
+
}
|
|
28
|
+
return directusTool.handler({ args, accountability, schema });
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// Local/client-side tool (schema only, executed on client)
|
|
33
|
+
return tool({
|
|
34
|
+
name: chatRequestTool.name,
|
|
35
|
+
description: chatRequestTool.description,
|
|
36
|
+
inputSchema: jsonSchema(chatRequestTool.inputSchema),
|
|
37
|
+
});
|
|
38
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { UIMessage } from 'ai';
|
|
2
|
+
/**
|
|
3
|
+
* Fixes tool calls with error states by copying rawInput to input field.
|
|
4
|
+
* This is required because error tool calls from the frontend use rawInput
|
|
5
|
+
* but the AI SDK's convertToModelMessages expects input to generate arguments
|
|
6
|
+
* for the OpenAI API.
|
|
7
|
+
* @param messages - Array of message objects from the chat request
|
|
8
|
+
* @returns Messages with error tool calls fixed, typed as UIMessage[]
|
|
9
|
+
*/
|
|
10
|
+
export declare const fixErrorToolCalls: (messages: {
|
|
11
|
+
[x: string]: unknown;
|
|
12
|
+
}[]) => UIMessage[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixes tool calls with error states by copying rawInput to input field.
|
|
3
|
+
* This is required because error tool calls from the frontend use rawInput
|
|
4
|
+
* but the AI SDK's convertToModelMessages expects input to generate arguments
|
|
5
|
+
* for the OpenAI API.
|
|
6
|
+
* @param messages - Array of message objects from the chat request
|
|
7
|
+
* @returns Messages with error tool calls fixed, typed as UIMessage[]
|
|
8
|
+
*/
|
|
9
|
+
export const fixErrorToolCalls = (messages) => {
|
|
10
|
+
return messages.map((msg) => {
|
|
11
|
+
if (msg['role'] === 'assistant' && msg['parts'] && Array.isArray(msg['parts'])) {
|
|
12
|
+
const fixedParts = msg['parts'].map((part) => {
|
|
13
|
+
if (typeof part === 'object' &&
|
|
14
|
+
part !== null &&
|
|
15
|
+
'type' in part &&
|
|
16
|
+
typeof part.type === 'string' &&
|
|
17
|
+
part.type.startsWith('tool-') &&
|
|
18
|
+
'state' in part &&
|
|
19
|
+
part.state === 'output-error' &&
|
|
20
|
+
'rawInput' in part &&
|
|
21
|
+
(!('input' in part) || part.input == null)) {
|
|
22
|
+
return { ...part, input: part.rawInput };
|
|
23
|
+
}
|
|
24
|
+
return part;
|
|
25
|
+
});
|
|
26
|
+
return { ...msg, parts: fixedParts };
|
|
27
|
+
}
|
|
28
|
+
return msg;
|
|
29
|
+
});
|
|
30
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { JSONSchema7 } from 'ai';
|
|
2
|
+
/**
|
|
3
|
+
* Checks whether an input object is *likely* a JSON Schema 7 object
|
|
4
|
+
* Limitations:
|
|
5
|
+
* - Does NOT validate nested schemas (e.g., inside `properties`, `items`, `definitions`, etc.)
|
|
6
|
+
* - Does NOT check for correct usage of combinators (`anyOf`, `allOf`, `oneOf`, `not`)
|
|
7
|
+
* - Does NOT verify required/optional keyword relationships (e.g., `required` only valid for object type)
|
|
8
|
+
* - Does NOT validate formats, patterns, or constraints (e.g., `pattern`, `minimum`, `maximum`, etc.)
|
|
9
|
+
* - Accepts both `definitions` and `$defs` for tolerance, but does not enforce draft-07 strictness
|
|
10
|
+
* - Only checks for presence of top-level schema keywords
|
|
11
|
+
* This is not a perfect validator, and does not check nested properties or full JSON Schema 7 compliance
|
|
12
|
+
*/
|
|
13
|
+
export declare const parseJsonSchema7: (schema: unknown) => JSONSchema7;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { fromZodError } from 'zod-validation-error';
|
|
3
|
+
const jsonType = z.enum(['null', 'boolean', 'object', 'array', 'number', 'integer', 'string']);
|
|
4
|
+
const maybeDraft7Url = z.url({ protocol: /^https?$/, hostname: /^json-schema\.org$/ }).refine((val) => {
|
|
5
|
+
try {
|
|
6
|
+
const u = new URL(val);
|
|
7
|
+
return u.pathname === '/draft-07/schema' && (u.hash === '' || u.hash === '#');
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}, { message: 'Must be the JSON Schema Draft-07 meta-schema URL' });
|
|
13
|
+
const JsonSchema7 = z
|
|
14
|
+
.object({
|
|
15
|
+
$schema: maybeDraft7Url.optional(),
|
|
16
|
+
$id: z.string().optional(),
|
|
17
|
+
$ref: z.string().optional(),
|
|
18
|
+
title: z.string().optional(),
|
|
19
|
+
description: z.string().optional(),
|
|
20
|
+
// common keywords
|
|
21
|
+
type: z.union([jsonType, z.array(jsonType).nonempty()]).optional(),
|
|
22
|
+
properties: z.record(z.string(), z.any()).optional(),
|
|
23
|
+
required: z.array(z.string()).optional(),
|
|
24
|
+
items: z.union([z.any(), z.array(z.any()).nonempty()]).optional(),
|
|
25
|
+
additionalProperties: z.union([z.boolean(), z.any()]).optional(),
|
|
26
|
+
patternProperties: z.record(z.string(), z.any()).optional(),
|
|
27
|
+
enum: z.array(z.any()).optional(),
|
|
28
|
+
const: z.any().optional(),
|
|
29
|
+
anyOf: z.array(z.any()).optional(),
|
|
30
|
+
allOf: z.array(z.any()).optional(),
|
|
31
|
+
oneOf: z.array(z.any()).optional(),
|
|
32
|
+
not: z.any().optional(),
|
|
33
|
+
// draft-07 naming; accept both for tolerance
|
|
34
|
+
definitions: z.record(z.string(), z.any()).optional(),
|
|
35
|
+
$defs: z.record(z.string(), z.any()).optional(), // not draft-07, but appears in newer drafts
|
|
36
|
+
})
|
|
37
|
+
.refine((obj) => {
|
|
38
|
+
const keys = new Set(Object.keys(obj));
|
|
39
|
+
const schemaish = [
|
|
40
|
+
'type',
|
|
41
|
+
'properties',
|
|
42
|
+
'items',
|
|
43
|
+
'required',
|
|
44
|
+
'enum',
|
|
45
|
+
'const',
|
|
46
|
+
'anyOf',
|
|
47
|
+
'allOf',
|
|
48
|
+
'oneOf',
|
|
49
|
+
'not',
|
|
50
|
+
'$ref',
|
|
51
|
+
'additionalProperties',
|
|
52
|
+
'patternProperties',
|
|
53
|
+
'definitions',
|
|
54
|
+
'$defs',
|
|
55
|
+
];
|
|
56
|
+
return schemaish.some((k) => keys.has(k));
|
|
57
|
+
}, { message: 'No schema keywords found' });
|
|
58
|
+
/**
|
|
59
|
+
* Checks whether an input object is *likely* a JSON Schema 7 object
|
|
60
|
+
* Limitations:
|
|
61
|
+
* - Does NOT validate nested schemas (e.g., inside `properties`, `items`, `definitions`, etc.)
|
|
62
|
+
* - Does NOT check for correct usage of combinators (`anyOf`, `allOf`, `oneOf`, `not`)
|
|
63
|
+
* - Does NOT verify required/optional keyword relationships (e.g., `required` only valid for object type)
|
|
64
|
+
* - Does NOT validate formats, patterns, or constraints (e.g., `pattern`, `minimum`, `maximum`, etc.)
|
|
65
|
+
* - Accepts both `definitions` and `$defs` for tolerance, but does not enforce draft-07 strictness
|
|
66
|
+
* - Only checks for presence of top-level schema keywords
|
|
67
|
+
* This is not a perfect validator, and does not check nested properties or full JSON Schema 7 compliance
|
|
68
|
+
*/
|
|
69
|
+
export const parseJsonSchema7 = (schema) => {
|
|
70
|
+
const { success, error, data } = JsonSchema7.safeParse(schema);
|
|
71
|
+
if (!success) {
|
|
72
|
+
throw new Error(`Invalid JSON Schema passed: ${fromZodError(error).message}`);
|
|
73
|
+
}
|
|
74
|
+
return data;
|
|
75
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import { type GetPromptResult } from '@modelcontextprotocol/sdk/types.js';
|
|
3
3
|
import type { Request, Response } from 'express';
|
|
4
|
-
import type {
|
|
4
|
+
import type { ToolConfig, ToolResult } from '../tools/types.js';
|
|
5
|
+
import type { MCPOptions } from './types.js';
|
|
5
6
|
export declare class DirectusMCP {
|
|
6
7
|
promptsCollection?: string | null;
|
|
7
8
|
systemPrompt?: string | null;
|
|
@@ -22,60 +23,52 @@ export declare class DirectusMCP {
|
|
|
22
23
|
toToolResponse(result?: ToolResult): {
|
|
23
24
|
[x: string]: unknown;
|
|
24
25
|
content: ({
|
|
25
|
-
[x: string]: unknown;
|
|
26
26
|
type: "text";
|
|
27
27
|
text: string;
|
|
28
28
|
_meta?: {
|
|
29
29
|
[x: string]: unknown;
|
|
30
30
|
} | undefined;
|
|
31
31
|
} | {
|
|
32
|
-
[x: string]: unknown;
|
|
33
|
-
data: string;
|
|
34
32
|
type: "image";
|
|
33
|
+
data: string;
|
|
35
34
|
mimeType: string;
|
|
36
35
|
_meta?: {
|
|
37
36
|
[x: string]: unknown;
|
|
38
37
|
} | undefined;
|
|
39
38
|
} | {
|
|
40
|
-
[x: string]: unknown;
|
|
41
|
-
data: string;
|
|
42
39
|
type: "audio";
|
|
40
|
+
data: string;
|
|
43
41
|
mimeType: string;
|
|
44
42
|
_meta?: {
|
|
45
43
|
[x: string]: unknown;
|
|
46
44
|
} | undefined;
|
|
47
45
|
} | {
|
|
48
|
-
[x: string]: unknown;
|
|
49
|
-
type: "resource_link";
|
|
50
|
-
name: string;
|
|
51
46
|
uri: string;
|
|
47
|
+
name: string;
|
|
48
|
+
type: "resource_link";
|
|
52
49
|
description?: string | undefined;
|
|
53
|
-
title?: string | undefined;
|
|
54
50
|
mimeType?: string | undefined;
|
|
55
51
|
_meta?: {
|
|
56
52
|
[x: string]: unknown;
|
|
57
53
|
} | undefined;
|
|
58
54
|
icons?: {
|
|
59
|
-
[x: string]: unknown;
|
|
60
55
|
src: string;
|
|
61
56
|
mimeType?: string | undefined;
|
|
62
57
|
sizes?: string[] | undefined;
|
|
63
58
|
}[] | undefined;
|
|
59
|
+
title?: string | undefined;
|
|
64
60
|
} | {
|
|
65
|
-
[x: string]: unknown;
|
|
66
61
|
type: "resource";
|
|
67
62
|
resource: {
|
|
68
|
-
[x: string]: unknown;
|
|
69
|
-
text: string;
|
|
70
63
|
uri: string;
|
|
64
|
+
text: string;
|
|
71
65
|
mimeType?: string | undefined;
|
|
72
66
|
_meta?: {
|
|
73
67
|
[x: string]: unknown;
|
|
74
68
|
} | undefined;
|
|
75
69
|
} | {
|
|
76
|
-
[x: string]: unknown;
|
|
77
|
-
blob: string;
|
|
78
70
|
uri: string;
|
|
71
|
+
blob: string;
|
|
79
72
|
mimeType?: string | undefined;
|
|
80
73
|
_meta?: {
|
|
81
74
|
[x: string]: unknown;
|
|
@@ -87,6 +80,10 @@ export declare class DirectusMCP {
|
|
|
87
80
|
})[];
|
|
88
81
|
_meta?: {
|
|
89
82
|
[x: string]: unknown;
|
|
83
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
84
|
+
[x: string]: unknown;
|
|
85
|
+
taskId: string;
|
|
86
|
+
} | undefined;
|
|
90
87
|
} | undefined;
|
|
91
88
|
structuredContent?: {
|
|
92
89
|
[x: string]: unknown;
|
|
@@ -6,10 +6,9 @@ import { CallToolRequestSchema, GetPromptRequestSchema, InitializedNotificationS
|
|
|
6
6
|
import { render, tokenize } from 'micromustache';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { fromZodError } from 'zod-validation-error';
|
|
9
|
-
import { ItemsService } from '
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { findMcpTool, getAllMcpTools } from './tools/index.js';
|
|
9
|
+
import { ItemsService } from '../../services/index.js';
|
|
10
|
+
import { Url } from '../../utils/url.js';
|
|
11
|
+
import { findMcpTool, getAllMcpTools } from '../tools/index.js';
|
|
13
12
|
import { DirectusTransport } from './transport.js';
|
|
14
13
|
export class DirectusMCP {
|
|
15
14
|
promptsCollection;
|
|
@@ -167,7 +166,6 @@ export class DirectusMCP {
|
|
|
167
166
|
// calling tools
|
|
168
167
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
169
168
|
const tool = findMcpTool(request.params.name);
|
|
170
|
-
let sanitizedQuery = {};
|
|
171
169
|
try {
|
|
172
170
|
if (!tool || (tool.name === 'system-prompt' && this.systemPromptEnabled === false)) {
|
|
173
171
|
throw new InvalidPayloadError({ reason: `"${request.params.name}" doesn't exist in the toolset` });
|
|
@@ -196,18 +194,11 @@ export class DirectusMCP {
|
|
|
196
194
|
if (!isObject(args)) {
|
|
197
195
|
throw new InvalidPayloadError({ reason: '"arguments" must be an object' });
|
|
198
196
|
}
|
|
199
|
-
if ('action' in args && args['action'] === 'delete'
|
|
197
|
+
if (this.allowDeletes === false && 'action' in args && args['action'] === 'delete') {
|
|
200
198
|
throw new InvalidPayloadError({ reason: 'Delete actions are disabled' });
|
|
201
199
|
}
|
|
202
|
-
if ('query' in args && args['query']) {
|
|
203
|
-
sanitizedQuery = await sanitizeQuery({
|
|
204
|
-
fields: args['query']['fields'] || '*',
|
|
205
|
-
...args['query'],
|
|
206
|
-
}, req.schema, req.accountability);
|
|
207
|
-
}
|
|
208
200
|
const result = await tool.handler({
|
|
209
201
|
args,
|
|
210
|
-
sanitizedQuery,
|
|
211
202
|
schema: req.schema,
|
|
212
203
|
accountability: req.accountability,
|
|
213
204
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface Prompt {
|
|
2
|
+
name: string;
|
|
3
|
+
system_prompt?: string | null;
|
|
4
|
+
description?: string;
|
|
5
|
+
messages: {
|
|
6
|
+
role: 'user' | 'assistant';
|
|
7
|
+
text: string;
|
|
8
|
+
}[];
|
|
9
|
+
}
|
|
10
|
+
export interface MCPOptions {
|
|
11
|
+
promptsCollection?: string;
|
|
12
|
+
allowDeletes?: boolean;
|
|
13
|
+
systemPromptEnabled?: boolean;
|
|
14
|
+
systemPrompt?: string | null;
|
|
15
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { UnsupportedMediaTypeError } from '@directus/errors';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
2
4
|
import { z } from 'zod';
|
|
3
|
-
import { AssetsService } from '
|
|
4
|
-
import { FilesService } from '
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { AssetsService } from '../../../services/assets.js';
|
|
6
|
+
import { FilesService } from '../../../services/files.js';
|
|
7
|
+
import { requireText } from '../../../utils/require-text.js';
|
|
8
|
+
import { defineTool } from '../define-tool.js';
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
10
|
const AssetsValidateSchema = z.strictObject({
|
|
8
11
|
id: z.string(),
|
|
9
12
|
});
|
|
@@ -12,7 +15,7 @@ const AssetsInputSchema = z.object({
|
|
|
12
15
|
});
|
|
13
16
|
export const assets = defineTool({
|
|
14
17
|
name: 'assets',
|
|
15
|
-
description:
|
|
18
|
+
description: requireText(resolve(__dirname, './prompt.md')),
|
|
16
19
|
annotations: {
|
|
17
20
|
title: 'Directus - Assets',
|
|
18
21
|
},
|