Fix ticket reply 400 and add attachment support for replies and notesFix/reply and attachments - #32
Open
vribeirocmp wants to merge 5 commits into
Open
vribeirocmp wants to merge 5 commits into
vribeirocmp wants to merge 5 commits into
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
send_ticket_replyalways sendingfrom_email(defaulted to a bogushelpdesk@<domain>that isn't a configured support email), which madeFreshservice reject every reply with HTTP 400.
from_emailis now omittedunless explicitly provided, so Freshservice uses the account's default
support email. Verified live: reply posts with
from_email: helpdesk@<domain>.com.attachments(single path or list of local file paths) tosend_ticket_replyandcreate_ticket_note; sends multipart/form-data withattachments[]parts and per-file content-type detection (FS limit 15 files / 40 MB).privateflag tocreate_ticket_note(defaultFalse= public note therequester sees;
True= internal).detailson HTTP errors for easier debugging.Test
python -m py_compileclean.correct account support email.