fix(server): scope loadingMessage wrap/strip to AI-chat callers (#19896)

## Summary

MCP tool execution crashed with \`Cannot destructure property
'loadingMessage' of 'parameters' as it is undefined\` whenever
\`execute_tool\` was called without an inner \`arguments\` field. Root
cause: \`loadingMessage\` is an AI-chat UX affordance (lets the LLM
narrate progress so the chat UI can show "Sending email…") but it was
being wrapped into **every** tool schema — including those advertised to
external MCP clients — and \`dispatch\` unconditionally stripped it,
crashing on \`undefined\` args.

The fix scopes the wrap/strip pair to AI-chat callers only:

- Pair wrap and strip inside \`hydrateToolSet\` (they belong together).
- New \`includeLoadingMessage\` option on \`hydrateToolSet\` /
\`getToolsByName\` / \`getToolsByCategories\` (default \`true\` so
AI-chat behavior is unchanged).
- MCP opts out → external clients see clean inputSchemas without a
required \`loadingMessage\` field.
- \`dispatch\` no longer strips; args default to \`{}\` defensively.
- \`execute_tool\` defaults \`arguments\` to \`{}\` at the LLM boundary.

## Test plan

- [x] \`npx nx typecheck twenty-server\` passes
- [x] \`npx oxlint\` clean on changed files
- [x] \`npx jest mcp-protocol mcp-tool-executor\` — 23/23 tests pass
- [ ] Manually: call \`execute_tool\` via MCP with and without inner
\`arguments\` — verify no crash, endpoints execute
- [ ] Manually: inspect MCP \`tools/list\` response — verify
\`search_help_center\` schema no longer contains \`loadingMessage\`
- [ ] Regression: AI chat still streams loading messages as the LLM
calls tools

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Félix Malfait
2026-04-20 21:43:16 +02:00
committed by GitHub
parent 83bc6d1a1b
commit 13afef5d1d
5 changed files with 45 additions and 18 deletions
@@ -118,6 +118,7 @@ export class McpProtocolService {
const preloadedTools = await this.toolRegistry.getToolsByName(
COMMON_PRELOAD_TOOLS,
toolContext,
{ includeLoadingMessage: false },
);
return {