Skip to content

MCP tool-name prefix: request fails with 500 when the model calls a tool without the toolset prefix, and the app system prompt is dropped when the client sends its own #541

Description

@andrii-novikov

QuickApps version

latest (0.8.0)

What steps will reproduce the bug?

  1. Register an MCP toolset in DIAL Core whose display name is My MCP, exposing a tool named validate_sql.
  2. Build a Quick App with an orchestrator model (reproduced with anthropic.claude-sonnet-4-20250514-v1:0) and a single dial-mcp tool set referencing that toolset. Note that dial-mcp tool sets carry no name in the manifest, so the prefix is taken from the toolset's display name in Core.
  3. QuickApps registers the tool as My_MCP_validate_sql (mcp_tooling/_mcp_tool_initializer.py:268-275, sanitize_toolname("My MCP" + "_" + tool.name)), and this is the name sent to the model in tools[].
  4. Ask the app something that makes the model call the tool, from two different front ends:
    • DIAL Chat, where the client sends no system message of its own;
    • an embedded widget (in our case the DIAL Admin analytics query builder) that sends its own system message as the first message of the request, e.g. {"role": "system", "content": "The user is building a query against ..."} followed by the user message.

From the widget the request fails within one orchestrator iteration; from Chat the same app usually succeeds.

What is the expected behavior?

  • A tool name the model got wrong should not abort the whole request. QuickApps should return a tool-result error to the model (naming the tool that does not exist, and ideally the available names) so the model can retry on the next iteration, exactly as it recovers from any other tool failure. Optionally, an unprefixed name that matches exactly one registered tool by suffix (*_<name>) should resolve to it.
  • The system prompt configured in the app manifest should reach the model regardless of whether the client supplied a system message of its own. A client-supplied system message should be combined with the app's own prompt, not silently replace it.

What do you see instead?

The model calls validate_sql instead of My_MCP_validate_sql, and the entire request fails with HTTP 500:

DEBUG | quickapp.core.agent.orchestrator | Agent requests internal tool calls: ['validate_sql']
ERROR | quickapp.core.agent.tool_executor | Model requested unknown tool(s) ['validate_sql']; registered=['My_MCP_describe_entity', ..., 'My_MCP_validate_sql', 'internal_skills_read_skill', 'internal_timeawareness_current_timestamp']
WARNING | quickapp.core.agent.orchestrator | Orchestrator interrupted by Unknown tool(s) requested by the model: ['validate_sql'], saving state before re-raising
Traceback (most recent call last):
  File "quickapp/core/application/_quick_app_completion.py", line 91, in chat_completion
  File "quickapp/core/agent/orchestrator.py", line 157, in invoke
  File "quickapp/core/agent/orchestrator.py", line 238, in _run_iteration
  File "quickapp/core/agent/tool_executor.py", line 50, in execute
RuntimeError: Unknown tool(s) requested by the model: ['validate_sql']
INFO | quickapp.core.application._quick_app_completion | Request completed: outcome=failed, iterations=1, tool_calls=1

The user sees a generic 500 - Something went wrong with the execution of your request, and everything the model had already produced in that turn is lost.

Additional information

Two independent defects combine here.

1. ToolExecutor treats a wrong tool name as fatal. core/agent/tool_executor.py:42-49 raises RuntimeError for any name missing from its registry, which propagates out of the orchestrator loop and fails the request. Every other tool-level failure is reported back to the model and recovered from; this one is not, even though the model needs only to be told the name was wrong.

2. A client-supplied system message silently discards the app's configured system prompt. core/agent/_messages_transformers.py:29-33:

if len(messages) > 0 and messages[0].role != Role.SYSTEM:
    return [Message(role=Role.SYSTEM, content=system_prompt)] + messages
return messages  # client sent a system message -> the app's prompt is never added

When the first incoming message has role system, the prompt assembled from all PromptPartProviders — the manifest's system_prompt, plus every provider-contributed part — is dropped without a log entry. This is what makes the two front ends behave differently: from Chat the app's instructions are in play, from the widget none of them reach the model. This affects any Quick App called by a client that sends its own system message, not just this MCP case.

The tool list itself is not the problem: the names in tools[] and the names in ToolExecutor's registry come from the same open_ai_tool object (core/agent/agent_module.py:169-177, common/staged_base_tool.py:77-80), so they cannot diverge, and stream accumulation concatenates name fragments correctly (common/chat_completion_stream/tool_call.py:34-44). The wrong name is what the model produced. But the tool-name prefix is derived from a toolset display name owned by another resource in Core, so app authors cannot control it from the manifest and cannot make it stable — which is why the failure needs to be recoverable rather than fatal.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions