Skip to content

fix(elixir): handle MCP elicitation requests - #1

Open
shaunr0b wants to merge 4 commits into
mainfrom
fix/mcp-elicitation-support
Open

fix(elixir): handle MCP elicitation requests#1
shaunr0b wants to merge 4 commits into
mainfrom
fix/mcp-elicitation-support

Conversation

@shaunr0b

@shaunr0b shaunr0b commented May 26, 2026

Copy link
Copy Markdown

Summary

  • handle mcpServer/elicitation/request through the existing user-input answer path
  • show MCP elicitation prompts clearly in the status dashboard

Verification

  • git diff --check
  • previously rebuilt elixir/bin/symphony from the patched checkout and restarted meta-symphony.service

Summary by CodeRabbit

  • New Features

    • Support for an additional MCP server user-input request type.
    • New tracker config: required_labels; client filters fetched issues by those labels.
    • Queue and dashboard now surface a distinct "waiting for capacity" state and messaging.
  • UI/Behavior

    • Metrics, queue wording, and retry rows updated to show Type/Status/Attempt and distinguish capacity-wait vs normal retries.
    • Orchestrator schedules slot-wait retries without advancing attempt count; API/state exposes retry kind/status accordingly.
  • Tests

    • Workflow and unit tests expanded for required-labels, label filtering, and capacity-wait observability.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Routes "mcpServer/elicitation/request" into the existing Codex user-input flow; adds tracker.required_labels config and threads required-label filtering through Linear client fetch/pagination/decoding with test helpers; introduces slot-wait retry type and surfaces capacity-wait entries in snapshot, presenter, dashboard, and tests.

Changes

MCP Server Elicitation Request Handling

Layer / File(s) Summary
Request routing and status display
elixir/lib/symphony_elixir/codex/app_server.ex, elixir/lib/symphony_elixir/status_dashboard.ex
AppServer adds a clause matching "mcpServer/elicitation/request" and delegates to maybe_auto_answer_tool_request_user_input/8. StatusDashboard adds humanize_codex_method/2 to extract and format params.question/params.prompt for display.

Tracker required_labels and Linear client filtering

Layer / File(s) Summary
Config schema and test-support wiring
elixir/lib/symphony_elixir/config/schema.ex, elixir/test/support/test_support.exs, elixir/test/symphony_elixir/core_test.exs
Adds required_labels (array of strings, default []) to Tracker embedded schema and casts it in changeset/2; exposes tracker_required_labels in generated test workflow YAML and asserts default/provided values in config tests.
Linear client: fetch flow, pagination, and decoding with label filter
elixir/lib/symphony_elixir/linear/client.ex
Threads a label_filter through candidate/IDs/states fetch flows and pagination helpers, updates decode_linear_response/3 and decode_linear_page_response/3 to apply filter_by_required_labels/2, and adds normalization utilities (required_label_filter/0, normalize_required_labels/1, normalize_label_name/1).
Linear client tests for required-label filtering
elixir/test/symphony_elixir/workspace_and_config_test.exs
Adds a mocked GraphQL test that supplies two issues with different labels and asserts only issues matching the required label set are returned via the test helper.

Capacity-wait retry scheduling and UI

Layer / File(s) Summary
Orchestrator: slot-wait scheduling and retry delay
elixir/lib/symphony_elixir/orchestrator.ex
Adds a slot-wait delay constant, threads delay_type into retry metadata, schedules slot-wait retries when no capacity is available, and selects slot-wait delay in retry_delay/2; includes delay_type in snapshot payloads.
Presenter: classify and suppress capacity-wait errors
elixir/lib/symphony_elixir_web/presenter.ex
Classifies retries as capacity_wait vs retry, includes kind in retry payloads, reports "waiting_for_slot" for capacity-wait entries, and suppresses last_error for capacity-wait items.
LiveView dashboard: Queue UI and helper labels
elixir/lib/symphony_elixir_web/live/dashboard_live.ex
Renames retry section to "Queue", ties queued metric to retrying count, updates table columns to use derived Type/Attempt/Status strings, and adds helper functions to render capacity-wait labels.
StatusDashboard formatting and observability tests
elixir/lib/symphony_elixir/status_dashboard.ex, elixir/test/symphony_elixir/extensions_test.exs, elixir/test/symphony_elixir/core_test.exs
Adds capacity_wait_entry?/1 and capacity-wait formatting path in status rendering, humanizes MCP elicitation requests, updates observability tests to expect "kind" labels and capacity-wait behavior, and adds an orchestrator unit test for slot-wait scheduling semantics.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I nibble through configs and flows,
I route the questions that the MCP nose knows.
Labels sorted, retries wait in line,
dashboards whisper "waiting_for_slot" just fine —
a rabbit claps for code that hums and grows. 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description lacks required sections from the template, including Context, TL;DR (expanded description), Alternatives, and a proper Test Plan with verification checkboxes. Add Context section explaining why MCP elicitation support is needed, expand TL;DR with a simple explanation, add Alternatives section, and include a formatted Test Plan with checkbox items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'handle MCP elicitation requests' directly matches the primary change shown in the PR, where the AppServer now recognizes and routes the 'mcpServer/elicitation/request' JSON-RPC method.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mcp-elicitation-support

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@elixir/lib/symphony_elixir/status_dashboard.ex`:
- Around line 1367-1377: The
humanize_codex_method("mcpServer/elicitation/request", payload) currently only
checks params.question and params.prompt and thus drops params.message; update
the function to also read params.message (e.g., via map_path(payload,
["params","message"])) and prefer it when present (or include it in the
fallback) so the returned string uses inline_text(message) just like
question/prompt; reference the existing humanize_codex_method and map_path
helpers and inline_text to implement this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6da31965-9cfa-4158-b654-8a03892e18ea

📥 Commits

Reviewing files that changed from the base of the PR and between 2c18518 and 191752f.

📒 Files selected for processing (2)
  • elixir/lib/symphony_elixir/codex/app_server.ex
  • elixir/lib/symphony_elixir/status_dashboard.ex

Comment on lines +1367 to +1377
defp humanize_codex_method("mcpServer/elicitation/request", payload) do
question =
map_path(payload, ["params", "question"]) ||
map_path(payload, ["params", "prompt"])

if is_binary(question) and String.trim(question) != "" do
"MCP server requires user input: #{inline_text(question)}"
else
"MCP server requires user input"
end
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for MCP elicitation payload examples and field names

# Search for MCP elicitation request payloads to see which field names are used
rg -A5 'mcpServer/elicitation/request' --type=elixir

# Also check test files for payload examples
rg -A10 'mcpServer.*elicitation' --glob '*test*.exs'

Repository: LegendsOfLearning/symphony

Length of output: 8246


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all occurrences of the MCP elicitation method and capture surrounding payload shapes
rg -n --hidden --no-ignore -S 'mcpServer/elicitation/request' elixir -g'*.ex' -g'*.exs'

echo "----"
# Look for params payload keys used with elicitation/request
rg -n --hidden --no-ignore -S 'mcpServer/elicitation/request.*params|params.*mcpServer/elicitation/request' elixir -g'*.ex' -g'*.exs' || true

echo "----"
rg -n --hidden --no-ignore -S '"(message|question|prompt)"' elixir -g'*.ex' -g'*.exs' | head -n 200

echo "----"
# Narrow: any mention of "question"/"prompt" in the context of elicitation
rg -n --hidden --no-ignore -S '(elicitation/request|turn_input_required|input required).{0,80}(question|prompt)' elixir -g'*.ex' -g'*.exs' || true

Repository: LegendsOfLearning/symphony

Length of output: 7377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect map_path implementation and nearby helpers in status_dashboard.ex
rg -n "defp map_path\\(" -S elixir/lib/symphony_elixir/status_dashboard.ex
sed -n '1,220p' elixir/lib/symphony_elixir/status_dashboard.ex | head -n 220

# Show the humanize_codex_method clauses around the mcp elicitation and requestUserInput handling
sed -n '1280,1425p' elixir/lib/symphony_elixir/status_dashboard.ex

# Inspect codex/app_server paths for mcpServer/elicitation/request where params are constructed/forwarded
sed -n '620,760p' elixir/lib/symphony_elixir/codex/app_server.ex
sed -n '1020,1160p' elixir/lib/symphony_elixir/codex/app_server.ex

# Search for creation of "mcpServer/elicitation/request" with params.question/prompt in the codebase
rg -n -S 'mcpServer/elicitation/request.*params|params.*(question|prompt)' elixir -g'*.ex' -g'*.exs'

Repository: LegendsOfLearning/symphony

Length of output: 20907


Show params.message for mcpServer/elicitation/request in the status dashboard.

humanize_codex_method("mcpServer/elicitation/request", ...) only reads params.question / params.prompt, but elixir/test/symphony_elixir/app_server_test.exs sends {"method":"mcpServer/elicitation/request","params":{"message":"Need operator input"}}. This will cause the dashboard to fall back to the generic text and drop the specific prompt.

Suggested change
   defp humanize_codex_method("mcpServer/elicitation/request", payload) do
     question =
+      map_path(payload, ["params", "message"]) ||
       map_path(payload, ["params", "question"]) ||
       map_path(payload, ["params", "prompt"])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
defp humanize_codex_method("mcpServer/elicitation/request", payload) do
question =
map_path(payload, ["params", "question"]) ||
map_path(payload, ["params", "prompt"])
if is_binary(question) and String.trim(question) != "" do
"MCP server requires user input: #{inline_text(question)}"
else
"MCP server requires user input"
end
end
defp humanize_codex_method("mcpServer/elicitation/request", payload) do
question =
map_path(payload, ["params", "message"]) ||
map_path(payload, ["params", "question"]) ||
map_path(payload, ["params", "prompt"])
if is_binary(question) and String.trim(question) != "" do
"MCP server requires user input: #{inline_text(question)}"
else
"MCP server requires user input"
end
end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@elixir/lib/symphony_elixir/status_dashboard.ex` around lines 1367 - 1377, The
humanize_codex_method("mcpServer/elicitation/request", payload) currently only
checks params.question and params.prompt and thus drops params.message; update
the function to also read params.message (e.g., via map_path(payload,
["params","message"])) and prefer it when present (or include it in the
fallback) so the returned string uses inline_text(message) just like
question/prompt; reference the existing humanize_codex_method and map_path
helpers and inline_text to implement this change.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
elixir/lib/symphony_elixir/status_dashboard.ex (1)

1398-1408: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add params.message check for MCP elicitation prompts.

The humanize_codex_method("mcpServer/elicitation/request", payload) clause only reads params.question and params.prompt, but the test suite uses params.message (as noted in previous reviews). Without this check, the dashboard will fall back to generic text and drop the specific prompt.

Suggested fix
   defp humanize_codex_method("mcpServer/elicitation/request", payload) do
     question =
+      map_path(payload, ["params", "message"]) ||
       map_path(payload, ["params", "question"]) ||
       map_path(payload, ["params", "prompt"])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@elixir/lib/symphony_elixir/status_dashboard.ex` around lines 1398 - 1408, The
clause humanize_codex_method("mcpServer/elicitation/request", payload) only
checks map_path(payload, ["params", "question"]) and ["params", "prompt"] but
tests supply the prompt under ["params", "message"], so update the payload
extraction to also check map_path(payload, ["params", "message"]) (e.g., try
question = map_path(..., ["params","question"]) || map_path(...,
["params","prompt"]) || map_path(..., ["params","message"])); keep the existing
is_binary and String.trim check and the inline_text call so the specific MCP
prompt is shown instead of the generic fallback.
🧹 Nitpick comments (1)
elixir/lib/symphony_elixir/status_dashboard.ex (1)

658-668: ⚡ Quick win

Consider removing redundant format_retry_summary/1 clause.

The second clause at line 666 appears to be defensive programming for non-map entries, but format_backoff_retry_summary/1 expects map fields (.issue_id, .identifier, .attempt, etc.) and will crash on non-maps anyway. Since the first clause at line 658 already handles all maps (both capacity-wait and backoff cases), the second clause is unreachable for valid retry entries.

Simplification
   defp format_retry_summary(%{} = retry_entry) do
     if capacity_wait_entry?(retry_entry) do
       format_capacity_wait_summary(retry_entry)
     else
       format_backoff_retry_summary(retry_entry)
     end
   end
-
-  defp format_retry_summary(retry_entry) do
-    format_backoff_retry_summary(retry_entry)
-  end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@elixir/lib/symphony_elixir/status_dashboard.ex` around lines 658 - 668,
Remove the redundant fallback clause for format_retry_summary/1: delete the
second clause "defp format_retry_summary(retry_entry) do
format_backoff_retry_summary(retry_entry) end" and keep only the map-matching
clause that dispatches to format_capacity_wait_summary/1 or
format_backoff_retry_summary/1; ensure no other code relies on a non-map arity
by running tests for format_retry_summary/1 and callers of format_retry_summary
to confirm behavior remains correct.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@elixir/lib/symphony_elixir/status_dashboard.ex`:
- Around line 1398-1408: The clause
humanize_codex_method("mcpServer/elicitation/request", payload) only checks
map_path(payload, ["params", "question"]) and ["params", "prompt"] but tests
supply the prompt under ["params", "message"], so update the payload extraction
to also check map_path(payload, ["params", "message"]) (e.g., try question =
map_path(..., ["params","question"]) || map_path(..., ["params","prompt"]) ||
map_path(..., ["params","message"])); keep the existing is_binary and
String.trim check and the inline_text call so the specific MCP prompt is shown
instead of the generic fallback.

---

Nitpick comments:
In `@elixir/lib/symphony_elixir/status_dashboard.ex`:
- Around line 658-668: Remove the redundant fallback clause for
format_retry_summary/1: delete the second clause "defp
format_retry_summary(retry_entry) do format_backoff_retry_summary(retry_entry)
end" and keep only the map-matching clause that dispatches to
format_capacity_wait_summary/1 or format_backoff_retry_summary/1; ensure no
other code relies on a non-map arity by running tests for format_retry_summary/1
and callers of format_retry_summary to confirm behavior remains correct.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7222a1fb-b136-4b92-b018-6ed3503423af

📥 Commits

Reviewing files that changed from the base of the PR and between 944a719 and 1b3a901.

📒 Files selected for processing (6)
  • elixir/lib/symphony_elixir/orchestrator.ex
  • elixir/lib/symphony_elixir/status_dashboard.ex
  • elixir/lib/symphony_elixir_web/live/dashboard_live.ex
  • elixir/lib/symphony_elixir_web/presenter.ex
  • elixir/test/symphony_elixir/core_test.exs
  • elixir/test/symphony_elixir/extensions_test.exs

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant