Skip to content

fix(prompts): accept string arguments for ticket_id - #310

Open
zoispag wants to merge 2 commits into
basher83:mainfrom
zoispag:fix/prompt-string-arguments
Open

fix(prompts): accept string arguments for ticket_id#310
zoispag wants to merge 2 commits into
basher83:mainfrom
zoispag:fix/prompt-string-arguments

Conversation

@zoispag

@zoispag zoispag commented Jul 30, 2026

Copy link
Copy Markdown

Problem

analyze_ticket and draft_response annotate ticket_id as int:

def analyze_ticket(ticket_id: int) -> str:
def draft_response(ticket_id: int, tone: str = "professional") -> str:

MCP prompt arguments are always transmitted as stringsPromptArgument has no type field. FastMCP's _convert_string_arguments (fastmcp/prompts/function_prompt.py) therefore coerces the incoming value to the annotation via pydantic, and any non-numeric string raises PromptError instead of rendering.

Clients that materialize MCP prompts into slash commands discover their arguments by rendering them with placeholder values. opencode issues prompts/get with "$1" / "$2", which fails:

PromptError: Could not convert argument 'ticket_id' with value '$1' to expected
type <class 'int'>. Error: 1 validation error for int
  Input should be a valid integer, unable to parse string as an integer
  [type=int_parsing, input_value='$1', input_type=str]

The result is that both prompts are unusable in such clients. escalation_summary is unaffected because group is annotated str | None, which is a useful control: it renders the same "$1" fine.

For context on how noisy this is in practice — my local logs carry 896 occurrences of this error, two per session across 448 sessions.

Fix

Annotate ticket_id as str. Both functions only interpolate it into the returned f-string — no arithmetic, no comparison — so this is behaviour-preserving. The docstrings already document that callers must pass the internal database ID rather than the display number, and that guidance is unchanged.

Tests

Added a parametrized regression test rendering all three prompts through the public mcp.render_prompt API with non-numeric arguments.

Verified it genuinely covers the bug — with the server.py change reverted:

FAILED tests/test_server.py::test_prompts_render_with_non_numeric_arguments[analyze_ticket-arguments0]
FAILED tests/test_server.py::test_prompts_render_with_non_numeric_arguments[draft_response-arguments1]
2 failed, 1 passed

escalation_summary passes both before and after, confirming the test is not trivially green.

Also updated test_prompt_handlers to pass ticket_id as a string, matching the new signature.

Full suite on this branch:

225 passed
Required test coverage of 86.0% reached. Total coverage: 88.42%

uv run ruff format, uv run ruff check, and uv run mypy mcp_zammad are all clean.

Summary by CodeRabbit

  • Bug Fixes

    • Ticket analysis and response drafting now accept ticket IDs provided as strings, including non-numeric values.
    • Prompt placeholders are preserved correctly when clients provide string arguments.
  • Tests

    • Added coverage for string-based ticket IDs and placeholder handling.

MCP prompt arguments are always transmitted as strings; PromptArgument has
no type field. Annotating ticket_id as int made FastMCP's
_convert_string_arguments coerce the incoming value via pydantic, so any
non-numeric string raised PromptError and the prompt could not be rendered.

Clients that materialize MCP prompts into slash commands discover their
arguments by rendering them with placeholder values. opencode, for example,
issues prompts/get with "$1"/"$2", which fails:

  PromptError: Could not convert argument 'ticket_id' with value '$1' to
  expected type <class 'int'>

This made analyze_ticket and draft_response unusable in those clients, while
escalation_summary worked because group is annotated str | None.

Annotate ticket_id as str. Both prompts only interpolate it into the returned
f-string, so this is behaviour-preserving; the docstrings already document
that callers must pass the internal database ID.

Add a parametrized regression test that renders all three prompts with
non-numeric arguments. It fails on analyze_ticket and draft_response before
this change and passes for escalation_summary throughout.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@zoispag, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0b3fca42-e5b4-482a-9f03-15aaf606301a

📥 Commits

Reviewing files that changed from the base of the PR and between b115d07 and e700308.

📒 Files selected for processing (1)
  • tests/test_server.py

Walkthrough

The MCP analyze_ticket and draft_response prompts now declare string ticket IDs. Tests cover string handler inputs and preservation of non-numeric placeholder arguments during prompt rendering.

Changes

Prompt string argument support

Layer / File(s) Summary
Update prompt ticket ID contracts
mcp_zammad/server.py
The analyze_ticket and draft_response prompt signatures change ticket_id from int to str.
Validate string prompt arguments
tests/test_server.py
Async tests verify placeholder preservation and invoke prompt handlers with string ticket IDs and tone values.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: stephaneberle9

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the prompt change to accept string ticket_id arguments and follows conventional commit style.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions github-actions Bot added type:bug Something is not working correctly area:mcp-tools area:ci-cd Continuous integration and deployment pipelines area:python Python development and tooling labels Jul 30, 2026
@codacy-production

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 1 minor

Alerts:
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
1 new issue

Category Results
Documentation 1 minor

View in Codacy

🟢 Metrics 0 complexity

Metric Results
Complexity 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@tests/test_server.py`:
- Around line 235-256: Add tests covering the remaining untested Python paths to
raise overall coverage from 88.42% to at least 90%. Extend the existing async
prompt tests around mcp.render_prompt and inspect related server functions for
uncovered branches, preserving current behavior while exercising each missing
path.
- Line 237: Update the parametrization names near the `"name,arguments"` entry
to use a tuple of individual names, `("name", "arguments")`, so the test
satisfies Ruff PT006 while preserving the existing parameter order.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 38b0733d-2032-42ee-ba02-51548d7b40fb

📥 Commits

Reviewing files that changed from the base of the PR and between 8873b2e and b115d07.

📒 Files selected for processing (2)
  • mcp_zammad/server.py
  • tests/test_server.py

Comment thread tests/test_server.py
Comment on lines +235 to +256
@pytest.mark.asyncio
@pytest.mark.parametrize(
"name,arguments",
[
("analyze_ticket", {"ticket_id": "$1"}),
("draft_response", {"ticket_id": "$1", "tone": "$2"}),
("escalation_summary", {"group": "$1"}),
],
)
async def test_prompts_render_with_non_numeric_arguments(name: str, arguments: dict[str, str]) -> None:
"""Prompt arguments arrive as strings, so they must not be coerced to int.

Clients that turn MCP prompts into slash commands render them with placeholder
values such as "$1" to discover their arguments. Annotating ticket_id as int made
FastMCP raise PromptError on those placeholders, so the prompts were unusable.
"""
result = await mcp.render_prompt(name, arguments)

rendered = result.messages[0].content.text # type: ignore[union-attr]
assert "$1" in rendered


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Raise total coverage to the required threshold.

The supplied PR results report 88.42% coverage, below the repository’s 90% minimum. Add coverage for the remaining Python paths before merging.

As per coding guidelines, maintain test coverage at 90%+ for all Python code.

🧰 Tools
🪛 Ruff (0.16.0)

[warning] 237-237: Wrong type passed to first argument of pytest.mark.parametrize; expected tuple

Use a tuple for the first argument

(PT006)

🤖 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 `@tests/test_server.py` around lines 235 - 256, Add tests covering the
remaining untested Python paths to raise overall coverage from 88.42% to at
least 90%. Extend the existing async prompt tests around mcp.render_prompt and
inspect related server functions for uncovered branches, preserving current
behavior while exercising each missing path.

Source: Coding guidelines

Comment thread tests/test_server.py Outdated
Addresses ruff PT006. The repo's ruff config does not select PT, so this was
not flagged locally, but the tuple form is clearer and costs nothing.
@zoispag

zoispag commented Jul 30, 2026

Copy link
Copy Markdown
Author

Thanks for the review. Addressed one, pushing back on the other.

PT006 — fixed in e700308. Worth noting it wasn't flagged locally because [tool.ruff.lint] select in pyproject.toml doesn't include PT; running ruff check --select PT tests/test_server.py surfaces 6 findings in that file, the other 5 pre-dating this PR. Happy to leave those alone or clean them up separately, whichever you prefer.

Coverage to 90% — I'd argue this is out of scope here. I measured both sides on the same machine:

tests coverage
8873b2e (main, before this PR) 222 88.42%
this branch 225 88.42%

This PR is coverage-neutral: it adds three tests and no source lines. The prompt bodies it touches were already exercised by test_prompt_handlers, so line coverage is unchanged.

The 90% figure also doesn't match what the repo enforces — pyproject.toml sets fail_under = 86, and the suite reports Required test coverage of 86.0% reached. main is already at 88.42%, i.e. below 90% independently of this change. Closing a pre-existing repo-wide 1.6-point gap seems like it belongs in its own PR rather than in a two-line annotation fix; I'd rather not pad this diff with unrelated tests to clear a gate that main doesn't meet either.

If you do want the 90% threshold enforced, bumping fail_under and backfilling coverage as a separate change would make that visible in CI rather than relying on review-time checks.

zoispag added a commit to zoispag/.dotfiles that referenced this pull request Aug 25, 2026
analyze_ticket and draft_response annotate ticket_id as int. MCP prompt
arguments are always strings on the wire, so FastMCP coerces them via pydantic
and rejects opencode's "$1" placeholder with PromptError, leaving both prompts
unusable. escalation_summary is unaffected (group: str | None), which confirms
the cause.

Point at zoispag/Zammad-MCP@fix/prompt-string-arguments until
basher83/Zammad-MCP#310 lands, then revert to basher83.

This pin was applied once before and lost during later opencode config edits;
committing it so it survives checkouts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:ci-cd Continuous integration and deployment pipelines area:python Python development and tooling type:bug Something is not working correctly

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant