Skip to content

Commit bd4eedf

Browse files
nearestnaborsclaude
andcommitted
Add x_list_conversations text-only tool for recipes
MCP Apps (rich UI) don't work in Goose recipes (500 errors on /agent/read_resource). Created x_list_conversations that returns plain text with markdown links, suitable for scheduled automation. - Add xListConversations() function in conversations.ts - Register x_list_conversations tool in server.ts (no _meta.ui) - Update x-conversations.yaml recipe to use new tool - Update test to match recipe changes - Update README and CLAUDE.md tool tables Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent c8094ae commit bd4eedf

5 files changed

Lines changed: 118 additions & 24 deletions

File tree

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,16 @@ Reply to @anthropic_devs saying I'll share slides after the talk
102102
103103
## Tools
104104
105-
| Tool | Description |
106-
| ------------------------ | ------------------------------------------------------- |
107-
| `x_auth_status` | Check authentication, show connect button if needed |
108-
| `x_conversations` | Show unreplied mentions as a conversation inbox |
109-
| `x_dismiss_conversation` | Dismiss a conversation (reappears on new activity) |
110-
| `x_draft_tweet` | Create draft with preview |
111-
| `x_post_tweet` | Post after approval |
112-
| `x_timeline_digest` | Fetch and summarize your Following timeline (past 24h) |
113-
| `x_show_tweet` | Display a single tweet as a rich card with reply option |
105+
| Tool | Description |
106+
| ------------------------ | ------------------------------------------------------------- |
107+
| `x_auth_status` | Check authentication, show connect button if needed |
108+
| `x_conversations` | Show unreplied mentions as a rich UI conversation inbox |
109+
| `x_list_conversations` | List unreplied mentions as text (for scheduled recipes) |
110+
| `x_dismiss_conversation` | Dismiss a conversation (reappears on new activity) |
111+
| `x_draft_tweet` | Create draft with preview |
112+
| `x_post_tweet` | Post after approval |
113+
| `x_timeline_digest` | Fetch and summarize your Following timeline (past 24h) |
114+
| `x_show_tweet` | Display a single tweet as a rich card with reply option |
114115
115116
## Timeline Digest Setup
116117

recipes/x-conversations.yaml

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,22 @@ description: |
77
instructions: |
88
You are a helpful assistant that helps the user manage their X conversations.
99
10-
Use the x_conversations tool to show the user their unreplied mentions
11-
and conversations from the past 7 days.
12-
13-
The UI will display:
14-
- Mentions from people awaiting your reply
15-
- Reply buttons to respond directly
16-
- Dismiss buttons to hide conversations (they reappear if there's new activity)
10+
Use the x_list_conversations tool to fetch unreplied mentions
11+
and conversations from the past 7 days. This returns text-formatted
12+
output suitable for scheduled recipes.
1713
1814
When presenting conversations:
1915
1. SKIP hate speech, spam, and low-value mentions
2016
2. Prioritize conversations from verified accounts, frequent collaborators,
2117
and those with higher engagement
2218
3. Flag any time-sensitive requests or questions
2319
4. Offer to draft replies for important conversations
20+
5. ALWAYS include the markdown links to each conversation so the user
21+
can click through to reply on X
2422
25-
After showing conversations, keep your response brief. The UI handles
26-
the interaction - don't explain what the user can already see.
23+
Format your summary clearly with the most important conversations first.
2724
28-
prompt: Show me my X conversations
25+
prompt: List my X conversations using x_list_conversations
2926

3027
activities:
3128
- Show my X conversations

src/__tests__/recipes/recipes.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ describe("Goose Recipes", () => {
115115
expect(hasInstructions || hasPrompt).toBe(true);
116116
});
117117

118-
test("instructions mention x_conversations tool", () => {
119-
expect(recipe.instructions).toContain("x_conversations");
118+
test("instructions mention x_list_conversations tool", () => {
119+
expect(recipe.instructions).toContain("x_list_conversations");
120120
});
121121

122122
test("has activities array", () => {

src/server.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ import {
2323

2424
// X tools
2525
import { xAuthStatus } from "./tools/auth-status.js";
26-
import { xConversations, xGetConversations } from "./tools/conversations.js";
26+
import {
27+
xConversations,
28+
xGetConversations,
29+
xListConversations,
30+
} from "./tools/conversations.js";
2731
import { xDismissConversation } from "./tools/dismiss-conversation.js";
2832
import { xDraftTweet } from "./tools/draft-tweet.js";
2933
import { xPostTweet } from "./tools/post-tweet.js";
@@ -46,13 +50,12 @@ const TOOLS: Tool[] = [
4650
{
4751
name: "x_auth_status",
4852
description:
49-
"Check X authentication status. IMPORTANT: The UI handles everything. Your ONLY response should be one short sentence. Do NOT explain, offer help, or ask follow-up questions.",
53+
"Check X authentication status. Shows OAuth button if not authenticated.",
5054
inputSchema: {
5155
type: "object",
5256
properties: {},
5357
required: [],
5458
},
55-
// _meta.ui links tool to UI resource
5659
_meta: {
5760
ui: {
5861
resourceUri: UI_RESOURCES.authButton,
@@ -214,6 +217,19 @@ const TOOLS: Tool[] = [
214217
},
215218
},
216219
},
220+
{
221+
name: "x_list_conversations",
222+
description:
223+
"List X conversations awaiting your reply as formatted text. " +
224+
"Use this in scheduled recipes or contexts where UI rendering is not available. " +
225+
"Returns conversations with links for easy access.",
226+
inputSchema: {
227+
type: "object",
228+
properties: {},
229+
required: [],
230+
},
231+
// No UI - returns text for recipes and scheduled tasks
232+
},
217233
];
218234

219235
// Tool handler dispatch
@@ -224,6 +240,7 @@ const toolHandlers: Record<string, ToolHandler> = {
224240
x_post_tweet: xPostTweet,
225241
x_conversations: xConversations,
226242
x_get_conversations: xGetConversations, // UI-only, returns full data
243+
x_list_conversations: xListConversations, // Text-only, for recipes
227244
x_dismiss_conversation: xDismissConversation,
228245
x_show_tweet: xShowTweet,
229246
x_timeline_digest: xTimelineDigest,

src/tools/conversations.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,3 +467,82 @@ export async function xGetConversations(params?: {
467467
],
468468
};
469469
}
470+
471+
/**
472+
* Format relative time for display
473+
*/
474+
function formatTimeAgo(isoDate: string): string {
475+
const date = new Date(isoDate);
476+
const now = new Date();
477+
const diffMs = now.getTime() - date.getTime();
478+
const diffMins = Math.floor(diffMs / 60_000);
479+
const diffHours = Math.floor(diffMins / 60);
480+
const diffDays = Math.floor(diffHours / 24);
481+
482+
if (diffMins < 60) {
483+
return `${diffMins}m ago`;
484+
}
485+
if (diffHours < 24) {
486+
return `${diffHours}h ago`;
487+
}
488+
return `${diffDays}d ago`;
489+
}
490+
491+
/**
492+
* Tool: x_list_conversations (text-only, for recipes)
493+
* Returns conversation data as formatted text instead of UI
494+
* Use this in recipes where MCP Apps are not supported
495+
*/
496+
export async function xListConversations(): Promise<unknown> {
497+
const result = await fetchConversations();
498+
499+
if (!result.success) {
500+
return result.content;
501+
}
502+
503+
const { conversations, username } = result.data;
504+
505+
if (conversations.length === 0) {
506+
return {
507+
content: [
508+
{
509+
type: "text",
510+
text: `No conversations awaiting reply for @${username}.`,
511+
},
512+
],
513+
};
514+
}
515+
516+
// Format conversations as text with links
517+
const lines: string[] = [
518+
`Found ${conversations.length} conversation${conversations.length === 1 ? "" : "s"} awaiting your reply (@${username}):`,
519+
"",
520+
];
521+
522+
for (const conv of conversations) {
523+
const timeAgo = formatTimeAgo(conv.created_at);
524+
const tweetUrl = `https://x.com/${conv.author_username}/status/${conv.tweet_id}`;
525+
const engagement = `${conv.like_count} likes, ${conv.retweet_count} RTs, ${conv.reply_count} replies`;
526+
527+
lines.push("---");
528+
lines.push(
529+
`[@${conv.author_username}](${tweetUrl}) (${conv.author_display_name}) - ${timeAgo}`
530+
);
531+
lines.push(conv.text);
532+
lines.push(`Engagement: ${engagement}`);
533+
}
534+
535+
lines.push("");
536+
lines.push(
537+
"To reply to a conversation, use the x_draft_tweet tool with the reply_to_id parameter."
538+
);
539+
540+
return {
541+
content: [
542+
{
543+
type: "text",
544+
text: lines.join("\n"),
545+
},
546+
],
547+
};
548+
}

0 commit comments

Comments
 (0)