feat: add list-conversations tool#184
Conversation
| const userRequests = Array.from(participantIds).map((userId) => | ||
| client.workspaceUsers.getUserById({ workspaceId, userId }, { batch: true }), | ||
| ) | ||
| const userResponses = await client.batch(...userRequests) |
There was a problem hiding this comment.
This will need to be split up, there's a limit of 10 batch requests at a time (this probably needs documenting in the SDK to prevent it from happening again).
You will need to batch the list of requests into groupings of 10 and do it that way.
| const participantNames = conversation.userIds.map( | ||
| (id) => participantLookup[id] ?? String(id), | ||
| ) | ||
| lines.push(`**Participants:** ${participantNames.join(', ')}`) |
There was a problem hiding this comment.
This could become a very long string if it's a big group (yes, they exist 😄), we should probably consider cutting this down to maybe 5 participants and do something like "Alan, Ellie, Ian, John, Donald, and x more)".
If we do this, we could probably re-work how we fetch the participants as well, take the first five ids, add them to a set (to remove duplication), then fetch the IDs for those. You'd still need to factor in the limit of 10 requests per batch, but at least it would drastically reduce how much data is required.
If a user wishes to see all participants of a conversation, that should require a different tool call, we don't need to display them all here.
| const listConversations = { | ||
| name: ToolNames.LIST_CONVERSATIONS, | ||
| description: | ||
| 'List conversations (direct messages) in a workspace. By default returns only active conversations; set includeArchived to true to also include archived conversations. Returns conversation IDs, titles, participant user IDs and names, archive status, last-active timestamps, snippets, and URLs.', |
There was a problem hiding this comment.
| 'List conversations (direct messages) in a workspace. By default returns only active conversations; set includeArchived to true to also include archived conversations. Returns conversation IDs, titles, participant user IDs and names, archive status, last-active timestamps, snippets, and URLs.', | |
| 'List conversations (direct messages) in a workspace. By default returns only active conversations; set includeArchived to true to also include archived conversations. Returns conversation IDs, titles, partial list of participant user IDs and names, archive status, last-active timestamps, snippets, and URLs.', |
Mirrors list-channels so agents can enumerate direct messages in a workspace. Returns active conversations by default, with includeArchived to also fetch archived ones; resolves participant names via a single batched user lookup.
…okups Addresses review feedback: - Resolve and display only the first 5 participants per conversation, with an "and N more" summary for larger groups. Callers needing the full participant list should load the conversation directly. - Split participant name lookups into batches of at most 10 to respect the Twist API's per-batch request limit, instead of issuing one unbounded batch. - Reflect the partial participant list in the tool description. Structured output now returns the first 5 participant IDs and names to match what is displayed; added tests for the truncation summary and batch chunking.
435ea43 to
03334f4
Compare
|
Pushed an update addressing the three notes (rebased onto main first):
Added tests for the truncation summary and the batch chunking (15 unique participants across three conversations splits into 10 + 5). Full suite green (225 tests). |
|
Thanks @scottlovegrove — the review points are addressed in the latest commit:
Also lowercased the PR title so the Validate Title check passes. Tests green (20 suites / 225). Ready for another look. |
scottlovegrove
left a comment
There was a problem hiding this comment.
Thanks @jgalea — the three points from my last review are all sorted: lookups now batch in chunks of 10, participants are capped at 5 with a deduplicated Set and the "and N more" suffix, and the description wording is in. The global dedup across conversations is a nice touch — a user shared across several DMs only gets resolved once.
Before this can merge, though, the new tool isn't fully registered. AGENTS.md lists seven required locations and three are missing:
src/index.ts—listConversationsisn't imported, isn't in thetoolsobject, and isn't in the named exports. Consumers importing from the package can't reach it.scripts/run-tool.ts— not in thetoolsrecord, sonpx tsx scripts/run-tool.ts list-conversationswon't find it.src/mcp-server.tsinstructions string — the tool is registered withregisterTool(), but there's no- **list-conversations**: …bullet in the LLM instructions string like every other tool has, so agents get no usage guidance.
The reason CI stayed green is that only tool-annotations.test.ts self-guards registration, and that entry is present — the other three spots have no test covering them.
One non-blocking note for the structured output: userIds now holds only the first five IDs and participantNames drops any that didn't resolve, but unlike the text render there's no total-participant count, so a consumer reading the structured payload can't tell the list is partial. The description says "partial", which covers intent, but a totalParticipants field would make it machine-legible. Happy to leave that for a follow-up.
Once the three registration spots are in I'm happy to approve.
Summary
Adds a
list-conversationstool that mirrorslist-channels, letting agents enumerate the direct-message conversations in a workspace.Today the server exposes
load-conversation(by ID) and channel listing, but there is no way to discover which DMs exist in a workspace. The Twist inbox view only surfaces unread/active conversations, so agents reviewing recent activity miss group DMs that did not bubble up. With this tool an agent can fetch the full list, resolve participant names, and link directly to each conversation.Behavior
workspaceId(required),includeArchived(optional, defaults tofalse).includeArchivedisfalse: a singleclient.conversations.getConversations({ workspaceId })call.includeArchivedistrue: batches the active and archived requests in parallel and merges the results.workspaceUsers.getUserByIdcall across all unique user IDs in the result set.textContent(one section per conversation, with title orConversation <id>fallback, archived status, last-active timestamp, participants, snippet) and a typedstructuredContentpayload (type: 'list_conversations').readOnlyHint: true,destructiveHint: false,idempotentHint: true(matcheslist-channels).Test plan
npm run build— cleannpm test— 178 pass (16 new)npm run format:check— clean (oxlint + oxfmt)npm run type-check— cleanmcp-server.tsand covered bytool-annotations.test.tsStructuredOutputSchemaunion and exported asListConversationsOutputincludeArchivedactive-only and merged paths, error propagation