Skip to content

article email support - fixes #205#206

Open
Erudition wants to merge 3 commits intobasher83:mainfrom
Erudition:fix/add-article-email-support
Open

article email support - fixes #205#206
Erudition wants to merge 3 commits intobasher83:mainfrom
Erudition:fix/add-article-email-support

Conversation

@Erudition
Copy link
Copy Markdown

@Erudition Erudition commented Mar 11, 2026

Summary of Changes

  1. mcp_zammad/models.py: Added to, cc, subject, and content_type fields to the ArticleCreate Pydantic model. This allows the MCP server to accept these parameters.
  2. mcp_zammad/client.py: Updated the add_article method to accept these new parameters and include them in the payload sent to the Zammad API.
  3. tests/test_server.py:
    • Added a new test case test_add_article_with_email_fields to verify that email fields are correctly passed to the client.
    • Updated existing tests to account for the updated method signature.
    • Fixed test_tool_without_client and test_get_client_error to expect ConfigException instead of RuntimeError, matching the lazy initialization behavior in lazy client initialization and stdio transport corruption #204

Summary by CodeRabbit

  • New Features

    • Docker Compose configuration now available for streamlined containerized deployment
    • Article creation now supports email-specific fields: subject, to, CC, and content type
  • Improvements

    • Logging initialization enhanced for better visibility during startup
    • Client initialization improved for better error handling and reliability

…ruption

- Implement lazy initialization in get_client() to resolve "Zammad client not initialized" errors during stateless HTTP/SSE session teardowns (fixes basher83#202).
- Redirect initialization logs to stderr to prevent stdout corruption during stdio transport.
- Ensure logging configuration is applied immediately in the default entrypoint.
…dd_article

- Update ArticleCreate Pydantic model to include 'to', 'cc', 'subject', and 'content_type' fields.
- Update ZammadClient.add_article to accept and pass these email-related fields to Zammad API.
- Fix discrepancy between tool docstring and implementation that caused email sending to fail.
- Update tests to verify email support and accommodate lazy client initialization.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 11, 2026

Walkthrough

This PR introduces Docker Compose configuration, configures logging earlier in initialization, implements lazy client instantiation, and extends the article creation API with email-specific fields (subject, to, cc, content_type). Changes span configuration, models, client methods, server initialization, and corresponding test updates.

Changes

Cohort / File(s) Summary
Docker Configuration
docker-compose.yml
New Docker Compose service for zammad-mcp with environment variables for Zammad connection, MCP transport settings, and port mapping (9146→8000).
Logging & Client Initialization
mcp_zammad/__main__.py, mcp_zammad/server.py
Early logging configuration call added before config loading; lazy client instantiation in get_client() replaces RuntimeError; StreamHandler redirected to sys.stderr.
Article Metadata Extension
mcp_zammad/models.py, mcp_zammad/client.py
Four optional email-related fields added to article creation: subject, to, cc, and content_type. Implementation includes conditional payload augmentation in add_article method.
Test Updates
tests/test_server.py
Test expectations updated for expanded add_article signature; new test coverage for email fields; exception type changed from RuntimeError to ConfigException for missing client initialization.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • basher83/Zammad-MCP#96 — Modifies mcp_zammad/server.py with concurrent changes to _configure_logging function.

Suggested labels

💾 database, 👨‍🚀 crew-backend, ⚡ api, ✨ enhancement, 🐛 bug, 🏷️ auto-labeled

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main functionality being added (article email support) and references the associated issue (#205).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docker-compose.yml`:
- Around line 8-10: Replace the plaintext environment variable ZAMMAD_HTTP_TOKEN
with the file-backed secret option supported by the client: stop exporting
ZAMMAD_HTTP_TOKEN in the environment block and instead set
ZAMMAD_HTTP_TOKEN_FILE to the path where the secret will be mounted (e.g.,
/run/secrets/zammad_http_token), add a secrets: entry under the service that
references a Docker secret named zammad_http_token, and define that secret in
the compose file (or mark it external) pointing to a local file (e.g.,
./secrets/zammad_http_token); update any references to ZAMMAD_HTTP_TOKEN in the
compose file to use ZAMMAD_HTTP_TOKEN_FILE so the token is provided via the
secret mount rather than an environment variable.

In `@mcp_zammad/models.py`:
- Around line 325-328: The content_type field currently allows any string and
always causes sanitize_body() to escape the body, breaking text/html and
allowing unsupported values; change content_type to an explicit validated enum
(accept only "text/plain" and "text/html" and default to "text/plain") on the
model (the Field named content_type) and add a validator that rejects/normalizes
other values, then update sanitize_body() to conditionally process the body: if
content_type == "text/plain" escape/encode as before, if content_type ==
"text/html" run an HTML sanitizer (or a safe whitelist/stripper) instead of
escaping, and ensure unsupported values are rejected before reaching the API;
reference the Field content_type and the sanitize_body() function when making
these changes (also apply same validation to the similar fields around lines
333-337).

In `@mcp_zammad/server.py`:
- Around line 823-825: The lazy path in get_client() constructs ZammadClient()
directly, bypassing the startup setup in initialize() that loads .env and
verifies connectivity; instead, call the same setup routine used by initialize()
(or extract that logic into a private helper like _setup_client and invoke it
from both initialize() and get_client()) so the lazy branch loads file-based
config and runs the same auth/connectivity verification before assigning
self.client, and preserve the same error handling/logging behavior.
🪄 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

Run ID: 3a02436e-41c7-4505-a184-d22c34381cf8

📥 Commits

Reviewing files that changed from the base of the PR and between 7f81fa5 and 83238ad.

📒 Files selected for processing (6)
  • docker-compose.yml
  • mcp_zammad/__main__.py
  • mcp_zammad/client.py
  • mcp_zammad/models.py
  • mcp_zammad/server.py
  • tests/test_server.py

Comment thread docker-compose.yml
Comment thread mcp_zammad/models.py
Comment on lines +325 to +328
subject: str | None = Field(default=None, description="Article subject (for emails)")
to: str | None = Field(default=None, description="Email recipient (for email type)")
cc: str | None = Field(default=None, description="Email CC recipients")
content_type: str | None = Field(default=None, description="Content type (text/plain or text/html)")
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.

⚠️ Potential issue | 🟠 Major

content_type support is incomplete at the model boundary.

The new header/content fields are all plain strings right now, and sanitize_body() still escapes every body before it leaves the model. That breaks text/html articles by sending literal tags, while unsupported content_type values still go straight to the API. Validate content_type explicitly and make body escaping conditional on the selected content type.

As per coding guidelines, "Validate all user inputs", "Check that all fields have appropriate defaults and constraints.", and "Verify HTML sanitization is applied where needed."

Also applies to: 333-337

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp_zammad/models.py` around lines 325 - 328, The content_type field
currently allows any string and always causes sanitize_body() to escape the
body, breaking text/html and allowing unsupported values; change content_type to
an explicit validated enum (accept only "text/plain" and "text/html" and default
to "text/plain") on the model (the Field named content_type) and add a validator
that rejects/normalizes other values, then update sanitize_body() to
conditionally process the body: if content_type == "text/plain" escape/encode as
before, if content_type == "text/html" run an HTML sanitizer (or a safe
whitelist/stripper) instead of escaping, and ensure unsupported values are
rejected before reaching the API; reference the Field content_type and the
sanitize_body() function when making these changes (also apply same validation
to the similar fields around lines 333-337).

Comment thread mcp_zammad/server.py
Comment on lines +823 to +825
logger.debug("Zammad client not initialized, performing lazy initialization")
# Lazy initialization for cases where lifespan initialization hasn't finished
self.client = ZammadClient()
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.

⚠️ Potential issue | 🟠 Major

Lazy get_client() skips the startup setup path.

initialize() loads .env files and eagerly verifies the connection, but the new lazy branch goes straight to ZammadClient(). A request that hits this path before lifespan now ignores file-based config and moves auth/connectivity failures from startup into the request path. Reuse the same setup helper here instead of constructing the client directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp_zammad/server.py` around lines 823 - 825, The lazy path in get_client()
constructs ZammadClient() directly, bypassing the startup setup in initialize()
that loads .env and verifies connectivity; instead, call the same setup routine
used by initialize() (or extract that logic into a private helper like
_setup_client and invoke it from both initialize() and get_client()) so the lazy
branch loads file-based config and runs the same auth/connectivity verification
before assigning self.client, and preserve the same error handling/logging
behavior.

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