Federico Mariani created CAMEL-26396:
----------------------------------------

             Summary: camel-jbang-mcp - the server lists itself as a running 
integration, so camel_eval_expression times out on it and camel_control kill 
fails with an internal error
                 Key: CAMEL-26396
                 URL: https://issues.apache.org/jira/browse/CAMEL-26396
             Project: Camel
          Issue Type: Bug
          Components: camel-jbang, camel-jbang-mcp
    Affects Versions: 4.23.0
            Reporter: Federico Mariani
            Assignee: Federico Mariani


Found on main (4.24.0-SNAPSHOT, commit ffef175654d1); the line numbers below 
are from that commit. The same code is in the 4.23.0 release.

h3. Reproduction

An evaluation of coding agents on legacy-to-Camel migrations ran 
camel-jbang-mcp 4.24.0-SNAPSHOT (built at ffef175654d1) over stdio in a Linux 
container, with OpenCode + GLM 5.3. After a few {{camel_eval_expression}} calls 
that ran in the local scratch context ({{"evaluatedIn":"a local scratch context 
(no integration selected)"}}), the server's own JVM shows up in the process 
list:

{code}
camel_runtime_processes {}
-> {"pid":50,"name":"io.quarkus.runner.GeneratedMain","contextName":"camel-9"}
{code}

{{ps}} in the same container shows that pid 50 is the camel-jbang-mcp runner 
(pid 51 is another MCP server, 55 a third one):
{noformat}
agent  50 ... /opt/java/openjdk/bin/java 
--add-opens=java.base/java.lang=ALL-UNNAMED 
--add-opens=java.base/java.nio=ALL-UNNAMED ... -Dquarkus.log.lev...
{noformat}

>From then on a tool called without a name picks that "only running 
>integration", the server itself:

{code}
camel_eval_expression {"body":"\"abc\"","expression":". | 
length","language":"jq"}
-> No answer from the integration with pid 50 to the action eval within 10 
seconds
camel_control {"action":"kill","name":"50"}
-> MCP error -32603: Internal error
{code}

The same jq calls answered at once a few calls earlier, in the scratch context. 
With a real integration running next to it, the server makes two processes, so 
a call without a name is refused instead:

{code}
camel_control {"action":"stop"}
-> No integration selected: pass name (an integration name or pid); 
list_processes shows what runs, camel_run starts one
camel_runtime_processes {}
-> {"pid":50,"name":"io.quarkus.runner.GeneratedMain","contextName":"camel-12"}
   {"pid":2325,"name":"alert-system","contextName":"alert-system"}
{code}

Eval evidence (200 trials on 4.24.0-SNAPSHOT, 3,421 MCP calls): 8 failed calls, 
all GLM 5.3, in 3 trials:
* 6 x {{camel_eval_expression}} "No answer from the integration with pid 50/58 
... within 10 seconds" (60 s spent waiting). In one trial the next identical 
call worked locally, so the server's status file can also be there for a moment 
only.
* 1 x {{camel_control kill}} of the server's pid: {{MCP error -32603: Internal 
error}}.
* 1 x {{camel_control stop}} without a name refused because the server counts 
as a second integration.
* Worst case: after the internal error the agent ran {{kill -9 50}} in its 
shell, which killed camel-jbang-mcp. The trial went on for 73 more tool calls 
without any Camel MCP tool.

Not seen with 4.22.1 (no scratch-context evaluation and no {{camel_control}} 
there).

h3. Cause

# {{ExpressionEvaluator.evaluateWith}} 
({{camel-jbang-core/.../commands/ai/ExpressionEvaluator.java:196}}) starts a 
{{new DefaultCamelContext(false)}} inside the MCP server for every local 
evaluation. camel-cli-connector is on the server's classpath (dependency of 
camel-jbang-core, {{camel-jbang-core/pom.xml:104-107}}), and 
{{AbstractCamelContext}} starts a CLI connector for every context whose factory 
is enabled ({{core/camel-base-engine/.../AbstractCamelContext.java:2671-2680}}; 
{{DefaultCliConnectorFactory}} is enabled by default). So each scratch context 
writes {{~/.camel/<server pid>-status.json}} while it runs, which makes the 
server a "running integration" ({{ProcessFinder}} counts any live pid with a 
status file). The context name ({{camel-9}}, {{camel-12}}) is the counter of 
contexts this JVM has created.
# The file outlived the context in two of three trials. 
{{FileCliConnectorTransport.doStop}} cancels its status task with 
{{cancel(false)}} and then deletes the files 
({{dsl/camel-cli-connector/.../FileCliConnectorTransport.java:433-451}}), so a 
status write already running can recreate the file after the delete, and the 
daemon executor of each context writes to the same {{<pid>-*}} files. (Likely 
cause, not proven: the files show that the status file stayed, with the context 
not in a stopped phase.)
# Nothing keeps the server from selecting itself: {{ProcessFinder.findAll()}} 
scans with {{skipThisProcess=false}} 
({{camel-jbang-core/.../common/ProcessFinder.java:86-88, 146-160}}), and a pid 
is matched even when it is the current one. {{RuntimeHelper.findProcess(null)}} 
({{common/RuntimeHelper.java:80-89}}) and 
{{ToolContext.selectSingleProcessIfNone()}} 
({{commands/ai/ToolContext.java:89}}) use it, which {{camel_eval_expression}} 
calls when no name is given ({{commands/ai/AuthoringTools.java:400-404}}).
# {{IntegrationLauncher.control}} "kill" calls 
{{ProcessHandle.destroyForcibly()}} on the selected pid 
({{commands/ai/IntegrationLauncher.java:575-581}}); for the current process the 
JDK throws {{IllegalStateException("destroy of current process not allowed")}}, 
which is not a {{ToolExecutionException}}, so the client gets -32603.

h3. Fix

* Start the scratch context without the CLI connector: bind a disabled 
{{CliConnectorFactory}} (or a no-op one) in the scratch context's registry 
before {{context.start()}} in {{ExpressionEvaluator.evaluateWith}}, and the 
same for any other short-lived context the tools start inside the server.
* Never treat this JVM as an integration in the tool layer: skip 
{{ProcessHandle.current()}} in {{RuntimeHelper.discoverProcesses}} / 
{{findProcess}} (or in {{ProcessFinder.findAll}} and the pid match), so a stale 
file cannot make the server pick itself.
* {{camel_control}} refuses the server's own pid with a 
{{ToolExecutionException}} ("pid N is this MCP server, not an integration"), so 
no action on it ends as -32603.
* Optionally, in camel-cli-connector: stop the status task and wait for it 
before deleting the files in {{FileCliConnectorTransport.doStop}}, so a stopped 
context cannot leave a status file behind.
* Test: evaluate a jq or jsonpath expression locally, then assert 
{{camel_runtime_processes}} is empty and {{camel_eval_expression}} without a 
name still evaluates locally.




--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to