feat: add send-direct-message and delete-item tools#177
Open
jgalea wants to merge 1 commit intoDoist:mainfrom
Open
feat: add send-direct-message and delete-item tools#177jgalea wants to merge 1 commit intoDoist:mainfrom
jgalea wants to merge 1 commit intoDoist:mainfrom
Conversation
Adds two new tools: - send-direct-message: sends a private message to one or more workspace users by finding (or creating) the appropriate conversation. Closes a gap where the existing tools could only post threads in channels and had no way to discover conversation IDs to use with the reply tool. - delete-item: permanently deletes a thread, comment, or conversation message via a unified `targetType` parameter, mirroring the shape of the existing reply and react tools. Useful for cleaning up posts that were sent in error. Both tools follow the established structure: zod schemas in output-schemas.ts, registration in mcp-server.ts (including LLM guidelines), exports in index.ts, run-tool.ts entries, annotation expectations in the existing test, and per-tool unit tests with snapshot coverage. Test suite: 172 passing (was 161).
scottlovegrove
approved these changes
Apr 25, 2026
Collaborator
scottlovegrove
left a comment
There was a problem hiding this comment.
Reviewed — approve with one real fix + nits.
Issue: messageCount <= 1 heuristic in send-direct-message.ts:78-80 is wrong. getOrCreateConversation returns pre-send state, so a brand-new conversation has messageCount === 0, not 1. The comment ("the one we just sent") is also incorrect — the message hasn't been posted yet when the conversation is fetched. As written, an existing conversation with exactly 1 prior message gets flagged createdConversation: true.
Fix: change to === 0, update the comment, and add a messageCount: 1 test to lock the boundary (current tests only cover 0 and 12).
Nits:
wasNew=trueprinting "Conversation existed before: No" reads fine for the LLM but is confusing in code — consider renaming toexistedBeforeand flipping the ternary.delete-itemsnapshot only covers thethreadcase — add comment + message snapshots, they're free and lock per-type formatting.- No runtime guard against including the sender in
userIds. Documented in the schema description, so OK as a contract — Twist likely dedupes.
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
Adds two new tools that fill gaps in the current toolset:
send-direct-message— send a private DM to one or more workspace users. The currentreplytool can post to a conversation if you already have the ID, but there is no tool that exposes a conversation ID for an arbitrary user —fetch-inboxonly lists conversations with unread activity. This tool wrapsconversations.getOrCreateConversation+conversationMessages.createMessageso an LLM can simply say "DM these users" without round-tripping through inbox lookups.delete-item— permanently delete a thread, comment, or conversation message. Useful for cleaning up posts sent in error. Uses a unifiedtargetTypeparameter (thread/comment/message), matching the pattern already used byreplyandreact.Both tools surfaced when an LLM client (Claude Code via MCP) tried to send a private heads-up to a teammate after a workflow event and discovered there was no route to a 1-on-1 DM, then needed to clean up after posting in the wrong channel.
Implementation
Follows the existing add-a-tool checklist from
AGENTS.md:src/utils/tool-names.ts— addedSEND_DIRECT_MESSAGEandDELETE_ITEMconstantssrc/utils/output-schemas.ts— addedSendDirectMessageOutputSchema,DeleteItemOutputSchema, types, and union entriessrc/tools/send-direct-message.tsandsrc/tools/delete-item.tssrc/mcp-server.ts— imports,registerToolcalls, and tool usage guidelines added to the LLMinstructionsblocksrc/index.ts— imports,toolsobject, and named exportsscripts/run-tool.ts— imports andtoolsrecord entriessrc/tools/__tests__/tool-annotations.test.ts— annotation expectations for both toolssrc/tools/__tests__/send-direct-message.test.ts— new (5 tests, 1 snapshot)src/tools/__tests__/delete-item.test.ts— new (6 tests, 1 snapshot)Annotations
send-direct-message:readOnlyHint: false,destructiveHint: false,idempotentHint: falsedelete-item:readOnlyHint: false,destructiveHint: true,idempotentHint: falseNotes
The
send-direct-messagetool'screatedConversationflag uses a heuristic —messageCount <= 1after the call means the conversation was just created (only the message we just posted exists). The Twist API doesn't surface a "newly created" indicator ongetOrCreateConversation, so this is the cleanest signal available.Test plan
npm test— 172/172 passing (was 161 before)npm run build— cleannpm run format:check— cleanscripts/run-tool.ts:send-direct-messagecreated a conversation and posted to itdelete-itemdeleted a thread, then a second thread🤖 Generated with Claude Code