From 3960845eabde065aa3b620f10b4d0d2ce7bd1609 Mon Sep 17 00:00:00 2001 From: claude code agent 227 Date: Thu, 25 Jun 2026 04:52:05 +0300 Subject: [PATCH] feat(ai-chat): per-MCP-server instructions in the agent system prompt (#180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Admins can now give each EXTERNAL MCP server a free-text instruction ("how/ when to use this server's tools") that the agent receives in its SYSTEM PROMPT next to the tool descriptions — porting the built-in SERVER_INSTRUCTIONS idea to admin-configured servers. Trusted, admin-authored text (like a system prompt); NON-secret, so unlike headersEnc it IS returned in views/forms. - Migration: nullable `instructions text` on ai_mcp_servers (old rows = null = no guidance). Table type + repo insert/update (blank/whitespace -> null via blankToNull). DTO `@MaxLength(4000)`. Service threads it through McpServerView/toView. - mcp-clients: `McpServerInstruction { serverName, toolPrefix, instructions }` threaded through the toolset/cache/lease. Guidance is built ONLY for a server that actually connected AND contributed >=1 callable tool (the allowlist may filter all of them out) AND has non-blank text — so a guide never appears for tools the agent cannot call. Cached with the toolset, so an edit is picked up next turn via the existing CRUD cache invalidation. - System prompt: `buildMcpToolingBlock` renders an block INSIDE the safety sandwich (after context, before the trailing SAFETY_FRAMEWORK) so it informs tool choice but cannot override the rules; each section is headed by the server's `prefix_*` namespace. Empty/blank -> block omitted. The caller (ai-chat.service) now builds the external toolset BEFORE the prompt and passes external.instructions; client-handle lifecycle (close-once) unchanged. - Client: instructions field in types + a Textarea (autosize, maxLength 4000) in the MCP-server form with a namespace-prefix hint; i18n (en/ru). Tests across every layer (prompt block placement + both SAFETY copies; view blank->null; buildEntry includes guidance only for connected+>=1-tool+non-blank; DTO MaxLength; repo + integration round-trip; service wiring). Delegated impl reviewed (APPROVE); applied the import-type follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../public/locales/en-US/translation.json | 1 + .../public/locales/ru-RU/translation.json | 2 + .../components/ai-mcp-server-form.tsx | 29 +- .../services/ai-mcp-server-service.ts | 7 + .../src/core/ai-chat/ai-chat.prompt.spec.ts | 117 +++++- .../server/src/core/ai-chat/ai-chat.prompt.ts | 57 ++- .../src/core/ai-chat/ai-chat.service.spec.ts | 129 ++++++- .../src/core/ai-chat/ai-chat.service.ts | 345 +++++++++--------- .../external-mcp/dto/create-mcp-server.dto.ts | 9 + .../dto/mcp-server-instructions.dto.spec.ts | 75 ++++ .../external-mcp/dto/update-mcp-server.dto.ts | 7 + .../external-mcp/mcp-clients.service.ts | 91 ++++- .../external-mcp/mcp-instructions.spec.ts | 168 +++++++++ .../external-mcp/mcp-servers-to-view.spec.ts | 21 +- .../external-mcp/mcp-servers.service.ts | 8 + ...0625T120000-ai-mcp-servers-instructions.ts | 19 + .../repos/ai-chat/ai-mcp-server.repo.spec.ts | 30 +- .../repos/ai-chat/ai-mcp-server.repo.ts | 21 ++ .../database/types/ai-mcp-servers.types.ts | 5 + .../ai-mcp-server-repo.int-spec.ts | 81 ++++ 20 files changed, 1011 insertions(+), 211 deletions(-) create mode 100644 apps/server/src/core/ai-chat/external-mcp/dto/mcp-server-instructions.dto.spec.ts create mode 100644 apps/server/src/core/ai-chat/external-mcp/mcp-instructions.spec.ts create mode 100644 apps/server/src/database/migrations/20260625T120000-ai-mcp-servers-instructions.ts diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 95fbfc0c..cdad5023 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -710,6 +710,7 @@ "Authorization header": "Authorization header", "Tool allowlist": "Tool allowlist", "Optional. Leave empty to allow all tools the server exposes.": "Optional. Leave empty to allow all tools the server exposes.", + "Optional guidance for the agent on how and when to use this server's tools. Injected into the system prompt. The server's tools are namespaced as \"_*\".": "Optional guidance for the agent on how and when to use this server's tools. Injected into the system prompt. The server's tools are namespaced as \"_*\".", "Test": "Test", "Available tools": "Available tools", "No tools available": "No tools available", diff --git a/apps/client/public/locales/ru-RU/translation.json b/apps/client/public/locales/ru-RU/translation.json index 336e8688..e3d46ad3 100644 --- a/apps/client/public/locales/ru-RU/translation.json +++ b/apps/client/public/locales/ru-RU/translation.json @@ -751,6 +751,8 @@ "Manage API keys for all users in the workspace. View the API documentation for usage details.": "Управляйте API-ключами для всех пользователей в рабочем пространстве. Смотрите документацию по API для получения информации об использовании.", "View the API documentation for usage details.": "Смотрите документацию по API для получения информации об использовании.", "View the MCP documentation.": "Смотрите документацию по MCP.", + "Instructions": "Инструкции", + "Optional guidance for the agent on how and when to use this server's tools. Injected into the system prompt. The server's tools are namespaced as \"_*\".": "Необязательное указание агенту, как и когда использовать инструменты этого сервера. Добавляется в системный промпт. Инструменты сервера именуются с префиксом «<имя сервера>_*».", "Sources": "Источники", "AI Answers not available for attachments": "Ответы ИИ недоступны для вложений", "No answer available": "Ответ недоступен", diff --git a/apps/client/src/features/workspace/components/settings/components/ai-mcp-server-form.tsx b/apps/client/src/features/workspace/components/settings/components/ai-mcp-server-form.tsx index a3d07a94..f3beb39b 100644 --- a/apps/client/src/features/workspace/components/settings/components/ai-mcp-server-form.tsx +++ b/apps/client/src/features/workspace/components/settings/components/ai-mcp-server-form.tsx @@ -11,6 +11,7 @@ import { Switch, TagsInput, Text, + Textarea, TextInput, } from "@mantine/core"; import { useForm } from "@mantine/form"; @@ -35,6 +36,8 @@ const formSchema = z.object({ // Write-only secret buffer. Empty string means "do not change" (unless cleared). authHeader: z.string(), toolAllowlist: z.array(z.string()), + // Admin-authored prompt guidance (#180). Capped to mirror the DTO MaxLength. + instructions: z.string().max(4000), enabled: z.boolean(), }); @@ -63,6 +66,7 @@ function buildInitialValues(server?: IAiMcpServer): FormValues { toolAllowlist: Array.isArray(server?.toolAllowlist) ? server.toolAllowlist : [], + instructions: server?.instructions ?? "", enabled: server?.enabled ?? true, }; } @@ -124,6 +128,8 @@ export default function AiMcpServerForm({ transport: values.transport, url: values.url, toolAllowlist: values.toolAllowlist, + // Always sent: a blank value clears the stored guidance (server -> null). + instructions: values.instructions, enabled: values.enabled, }; // Only attach headers when set or explicitly cleared (omit => unchanged). @@ -135,6 +141,8 @@ export default function AiMcpServerForm({ transport: values.transport, url: values.url, toolAllowlist: values.toolAllowlist, + // Blank => server stores null (no guidance). + instructions: values.instructions, enabled: values.enabled, }; // On create, only a typed value matters (no prior stored headers). @@ -158,10 +166,7 @@ export default function AiMcpServerForm({ return ( - +