Repository navigation
fix(mcp): accept embedded custom fields without a type - #74
Merged
Merged
Conversation
Help Scout's conversation list/search embed returns custom fields as
{ id, name, value, text } with no `type`. customFieldSchema required
`type`, so every conversation-returning MCP tool (search_conversations,
search_by_customer, list_conversations, get_conversation) failed output
validation whenever a conversation had custom fields set.
Make `type` optional, declare the embed's `text` label, and mirror both
in the CustomField type. Adds regression coverage for all four tools.
Fixes #56
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NkDvdNG1AWbYCDzZ6XbXEz
stephendolan
marked this pull request as ready for review
October 1, 2026 13:08
CLBarajas
added a commit
to CLBarajas/helpscout-cli
that referenced
this pull request
Oct 20, 2026
…ted; stephendolan#56 fix adopted, fork copy dropped) Takes all four commits in main..upstream/main (99f3902): v2.19.0 (stephendolan#68, stephendolan#69) plus two unreleased ones after the tag (stephendolan#71, stephendolan#74). ADOPTED - stephendolan#74 (99f3902) customFields[].type optional + `text` declared. This closes our upstream issue stephendolan#56. Semantically identical to the fork's 20d18fe; customFieldSchema is now byte-identical to upstream's, and the fork's 6-line block comment (outside the conflict hunk) is removed with it. The fork's 20d18fe regression test is DROPPED: upstream's four end-to-end tests (search_conversations, search_by_customer, list_conversations, get_conversation) cover it, and its fixture held real customer data. - stephendolan#68 (b2e1236) `conversations assign` / MCP assign_conversation / updateConversationAssignee / stricter parseIdArg. Upstream reimplemented the fork's update --assignee; both send the same PATCH /assignTo op. Kept the fork's update / update_conversation beside it (broader: status, subject, customer, mailbox in one call). - stephendolan#71 (1fe99c8) @napi-rs/keyring 1.3.0 -> 2.0.0, vitest 4.1.10 -> 5.0.0. keyring 2.0.0 verified against the real macOS Keychain: reads all four existing helpscout-cli items byte-identically to 1.3.0, and a throwaway item round-tripped write/overwrite/delete across both versions (same item, no duplicate). bun.lock taken as auto-merged (upstream's lock + the fork's mime-types lines); --frozen-lockfile passes and install leaves it unchanged. - stephendolan#69 (0a8eb20) version 2.19.0. SILENT BREAKAGES FIXED (no conflict markers; the build accepts both) - updateConversationAssignee passed ONE patch object to the fork's array-typed updateConversation (8937fd4). tsup/esbuild builds it; at runtime `for...of` throws, so every assign would have failed. Upstream's CLI/MCP tests mock the method; only typecheck and upstream's two api-client assign tests see it. Wrapped in [op]. Same drift the v2.15.0 merge (45df349) hand-fixed for updateConversationStatus. - conversations.ts imported HelpScoutCliError twice (tsc TS2300). - stephendolan#68's file-level beforeAll raw-connected the singleton MCP server, so the fork's hazard-3 dialect lock ("labels every emitted schema 2020-12") threw "Already connected to a transport". Confirmed failing before the fix. The shared connection now goes through connectMcpServer (the production path that applies retargetSchemaDialect) and the dialect test reuses it, so a future raw connect fails that test instead of bypassing it. Conflict resolution (6 hunks / 3 files) - mcp/server.ts: customFieldSchema -> upstream's; test exports unioned (fork's four + getMcpServerForTesting). - mcp/server.test.ts: fork stephendolan#56 test dropped; subject-less, tool-parity and scope-floor tests kept; upstream's four assign_conversation tests and the stephendolan#74 embed describe kept; subject-less comment repointed. - lib/api-client.test.ts: both hunks unioned (all fork tests + upstream's assign / unassign PATCH-shape tests). Gates: baseline at 6f4dafb 163/163 (vitest 4.1.10); merged 181/181 under vitest 5.0.0 (-1 dropped, +19 upstream). typecheck 0, lint 0, format at the pre-existing 12. Beyond the gates: literal registerTool 110 -> 111 (only +assign_conversation, zero lost, no duplicates); built binary loads (no Commander duplicate), reports 2.19.0; assign's three invalid-argument paths fail before any API call. Real-stdio tools/list: 173 tools, all 25 outputSchemas labelled 2020-12 and compiling under Ajv2020; live read-only get_conversation (#188422, subject-less, type-less customFields), search_conversations and list_draft_replies validate under Ajv2020. Keychain auth with env fallbacks unset passes on keyring 2.0.0.
CLBarajas
added a commit
to CLBarajas/helpscout-cli
that referenced
this pull request
Oct 20, 2026
UPSTREAM_SYNC: current state written after the fast-forward (main at the merge 5ca0601 + this commit; 0 behind / 88 ahead of upstream 99f3902; 6 ahead of origin/main; rollback tag/branch pre-upstream-2.19.0 at 6f4dafb, plus the bun install step a rollback also needs). Merge-history row for v2.19.0 + stephendolan#71/stephendolan#74; pending back to empty; the keyring 2.0.0 Keychain verification; the attribution finding the open-items table referred to but never recorded; unmerged upstream branches re-derived (all superseded). Hazards: hazard 1 recurred as signature drift (an upstream caller of the fork's array-typed updateConversation; same drift 45df349 hand-fixed at v2.15.0), plus a duplicate import. New hazard 4: a test-harness double connect that broke the hazard-3 dialect lock. Hazard 3 phase corrected per modelcontextprotocol/typescript-sdk#2721: calls fail client-side, the tools stay listed. The v2.18.3 tool-count method corrected (107 -> 110 by literal name) and the extractor pinned. Gates: bun run test is vitest, bun test is not, and upstream CI runs bun test; format baseline is now 12. UPSTREAM_ISSUES_PLAN: stephendolan#56 fixed upstream by stephendolan#74 and merged, our test dropped (its fixture held customer data); stephendolan#73 traced and not ours; the feature candidates annotated with what upstream shipped on its own. API_COVERAGE: assign/unassign, 173 tools / 25 outputSchemas, the draft status-guard and attribution text corrected, a duplicate System Users section removed. V3_ROADMAP: issue status and counts. The same hazard-3 phase wording is fixed in the retargetSchemaDialect docblock and the dialect-test comment (comments only). CLAUDE.md's test command is bun run test.
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.
Fixes #56.
Problem
Help Scout's conversation list/search embed returns custom fields as
{ id, name, value, text }, with notype.customFieldSchemainsrc/mcp/server.tsrequiredtype. BecauseconversationSchemauses that schema, every conversation-returning MCP tool (search_conversations,search_by_customer,list_conversations,get_conversation) failed output validation whenever a conversation had custom fields set:.passthrough()(from #50) doesn't help here, because the field is missing rather than extra.Change
customFieldSchema:typeis now optional, and the embed'stextlabel is declared as optional.CustomFieldtype updated to match.mainand pass with this change.Verification
bun run typecheck,bun run lint,bun run format:check: cleanbun run test: 102 passed🤖 Generated with Claude Code
https://claude.ai/code/session_01NkDvdNG1AWbYCDzZ6XbXEz
Generated by Claude Code