Skip to content

Fix ticket reply 400 and add attachment support for replies and notesFix/reply and attachments - #32

Open
vribeirocmp wants to merge 5 commits into
effytech:mainfrom
vribeirocmp:fix/reply-and-attachments
Open

vribeirocmp wants to merge 5 commits into
effytech:mainfrom
vribeirocmp:fix/reply-and-attachments

Conversation

@vribeirocmp

Copy link
Copy Markdown

Summary

  • Fix send_ticket_reply always sending from_email (defaulted to a bogus
    helpdesk@<domain> that isn't a configured support email), which made
    Freshservice reject every reply with HTTP 400. from_email is now omitted
    unless explicitly provided, so Freshservice uses the account's default
    support email. Verified live: reply posts with from_email: helpdesk@<domain>.com.
  • Add attachments (single path or list of local file paths) to
    send_ticket_reply and create_ticket_note; sends multipart/form-data with
    attachments[] parts and per-file content-type detection (FS limit 15 files / 40 MB).
  • Add private flag to create_ticket_note (default False = public note the
    requester sees; True = internal).
  • Surface the API response body as details on HTTP errors for easier debugging.

Test

  • python -m py_compile clean.
  • Live: public reply sent to ticket #38828, delivered to the requester with the
    correct account support email.

Vinicius Ribeiro and others added 4 commits March 17, 2026 12:56
All @mcp.tool() functions were returning Python dicts, but the MCP SDK's
Pydantic output validation expects string results. This caused errors like:

  1 validation error for get_ticket_by_idOutput
  result
    Input should be a valid string

Changes:
- Changed all return type annotations from Dict[str, Any] to str
- Wrapped all response.json() returns with json.dumps()
- Wrapped all dict literal returns with json.dumps()
- Added proper try/except error handling to 18 functions that had none
The project was missing a [build-system] section, which caused `uv`
to skip installing entry points (project.scripts). This meant the
`freshservice-mcp` command was not available when running locally
via `uv run`, and `uv sync` would warn about skipping entry points.

Added hatchling as the build backend with the correct package path
for the src layout.
Add create, list, view, update, and delete tools for ticket time entries.
Previously only change time entries were supported. The Freshservice API
supports /api/v2/tickets/{id}/time_entries with the same interface.
send_ticket_reply always sent from_email, defaulting to a bogus
helpdesk@<domain> address that is not a configured support email, so
Freshservice rejected every reply with 400. Now from_email is omitted
unless explicitly provided, letting Freshservice use the account's
default support email.

Also:
- Add `attachments` (single path or list of local file paths) to
  send_ticket_reply and create_ticket_note. When present, the request is
  sent as multipart/form-data with attachments[] parts (images, PDFs,
  etc.); content type is guessed per file.
- Add `private` flag to create_ticket_note (default False = public note
  visible to the requester; True = internal note).
- Surface the API response body as `details` on HTTP errors for both
  tools to make future failures debuggable.
- Add get_auth_headers_multipart(), _coerce_to_list(), and
  _build_attachment_parts() helpers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
milliamp added a commit to milliamp/freshservice_mcp that referenced this pull request Jun 27, 2026
…of upstream PR effytech#32)

Two changes ported from effytech/freshservice_mcp PR effytech#32 (vribeirocmp:
fix/reply-and-attachments). The upstream PR targets the legacy server.py
which we've replaced; the relevant fixes are reapplied here:

1. **from_email bug fix.** manage_ticket(action='reply') used to default
   from_email to "helpdesk@<DOMAIN>", which Freshservice rejects with HTTP
   400 unless that exact address is a configured support mailbox. Now
   from_email is only sent when explicitly provided in payload; otherwise
   Freshservice uses the account's default support email.

2. **Attachment support on reply and add_note.** payload.attachments accepts
   a single local file path or a list of paths. When present, the request
   is sent as multipart/form-data via api_post_multipart with proper
   per-file content-type detection. Missing files surface as
   "Attachment not found: <path>" rather than raising.

   Freshservice limits: 15 files, 40 MB total per request.

Implementation:
  - http_client: get_auth_headers_multipart, build_attachment_parts,
    api_post_multipart helpers.
  - tickets.py: reply and add_note branches in _ticket_handler grow a
    multipart path; manage_ticket docstring documents the new payload
    keys (attachments, from_email guidance).
  - FRESHSERVICE_DOMAIN import dropped from tickets.py (no longer used).

Skipped from PR effytech#32: legacy server.py rewrites (we replaced that file),
private flag on notes (already supported via payload.private), and the
details-on-HTTP-error pattern (already in handle_error).
milliamp added a commit to milliamp/freshservice_mcp that referenced this pull request Jul 3, 2026
…ring payloads

Two live-reported bugs after the PR-effytech#32 port:

1. **httpx 0.28 multipart regression.** api_post_multipart was passing
   ``data`` as a list of tuples together with ``files``, which raises
   ``RuntimeError: Attempted to send an sync request with an AsyncClient
   instance.`` on httpx 0.28. Rebuild the form as a dict with list values
   for repeated keys (cc_emails[], bcc_emails[]) — that's the shape httpx
   0.28's AsyncClient accepts with files= today.

   Reproduced isolated:
     data=[('body','hi')] + files=[...]  → RuntimeError
     data={'body':'hi'}    + files=[...]  → 200 OK

2. **payload arriving as a JSON string.** Observed live:
     "payload": "{\"attachments\": [\"/Users/.../file.pdf\"]}"
   Some MCP clients serialize dict-typed args as JSON strings on the
   wire. Added a small coerce_payload() helper in _split.py that
   tolerates dict / JSON-string / None / malformed input, and wired it
   into the ticket/problem/release handlers where the payload dict is
   consumed.

Verified multipart POST end-to-end against httpbin.org (form fields +
files delivered correctly). All 71 tools still register cleanly.
…ting

Spec 1 adds 7 read-only knowledge base tools reaching subfolders, article
bodies, and attachment text extraction. Spec 2 hosts the server as a remote
streamable-http MCP endpoint for Foundry project lrsagenthub-dev-usw3.

Both are grounded in measurements against lrs.freshservice.com: folders nest
3 levels deep (55 in one category), article bodies reach 1.28 MB, the search
endpoint returns 4 MB for 30 hits, and description_text gives a 150x cheaper
plain-text body. Attachment extraction verified with pypdf and openpyxl on
real PDFs and spreadsheets.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant