discourse_list_user_posts
Retrieve user posts and replies from Discourse forums to analyze contributions, monitor activity, or gather content. Returns recent posts with pagination support for comprehensive user history.
Instructions
Get a list of user posts and replies from a Discourse instance, with the most recent first. Returns 30 posts per page by default. Use the page parameter to paginate (page 0 = offset 0, page 1 = offset 30, etc.).
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| username | Yes | ||
| page | No |
Implementation Reference
- Handler function that fetches user posts and replies from Discourse API using user_actions endpoint with filter=4,5, processes them into formatted markdown blocks with topic links, handles pagination, and returns content or error.async ({ username, page }, _extra: any) => { try { const { base, client } = ctx.siteState.ensureSelectedSite(); const offset = (page || 0) * 30; // The filter parameter 4,5 corresponds to posts and replies const data = (await client.get( `/user_actions.json?offset=${offset}&username=${encodeURIComponent(username)}&filter=4,5` )) as any; const userActions = data?.user_actions || []; if (userActions.length === 0) { return { content: [{ type: "text", text: page && page > 0 ? `No more posts found for @${username} at page ${page}.` : `No posts found for @${username}.` }] }; } const posts = userActions.map((action: any) => { const excerpt = action.excerpt || ""; const truncated = action.truncated ? "..." : ""; const date = action.created_at || ""; const topicTitle = action.title || ""; const topicSlug = action.slug || ""; const topicId = action.topic_id || ""; const postNumber = action.post_number || ""; const categoryId = action.category_id || ""; const postUrl = `${base}/t/${topicSlug}/${topicId}/${postNumber}`; return [ `**${topicTitle}**`, `Posted: ${date}`, `Topic: ${postUrl}`, categoryId ? `Category ID: ${categoryId}` : undefined, excerpt ? `\n${excerpt}${truncated}` : undefined, ].filter(Boolean).join("\n"); }); const totalShown = userActions.length; const pageInfo = page && page > 0 ? ` (page ${page})` : ""; const header = `Showing ${totalShown} posts for @${username}${pageInfo}:\n\n`; const footer = totalShown === 30 ? `\n\nTo see more posts, use page ${(page || 0) + 1}.` : ""; return { content: [{ type: "text", text: header + posts.join("\n\n---\n\n") + footer }] }; } catch (e: any) { return { content: [{ type: "text", text: `Failed to get posts for ${username}: ${e?.message || String(e)}` }], isError: true }; } }
- Zod input schema validating username as non-empty string and optional page as non-negative integer.const schema = z.object({ username: z.string().min(1), page: z.number().int().min(0).optional(), });
- src/tools/builtin/list_user_posts.ts:10-82 (registration)Direct registration of the tool via server.registerTool, including name, metadata, input schema, and handler function.server.registerTool( "discourse_list_user_posts", { title: "List User Posts", description: "Get a list of user posts and replies from a Discourse instance, with the most recent first. Returns 30 posts per page by default. Use the page parameter to paginate (page 0 = offset 0, page 1 = offset 30, etc.).", inputSchema: schema.shape, }, async ({ username, page }, _extra: any) => { try { const { base, client } = ctx.siteState.ensureSelectedSite(); const offset = (page || 0) * 30; // The filter parameter 4,5 corresponds to posts and replies const data = (await client.get( `/user_actions.json?offset=${offset}&username=${encodeURIComponent(username)}&filter=4,5` )) as any; const userActions = data?.user_actions || []; if (userActions.length === 0) { return { content: [{ type: "text", text: page && page > 0 ? `No more posts found for @${username} at page ${page}.` : `No posts found for @${username}.` }] }; } const posts = userActions.map((action: any) => { const excerpt = action.excerpt || ""; const truncated = action.truncated ? "..." : ""; const date = action.created_at || ""; const topicTitle = action.title || ""; const topicSlug = action.slug || ""; const topicId = action.topic_id || ""; const postNumber = action.post_number || ""; const categoryId = action.category_id || ""; const postUrl = `${base}/t/${topicSlug}/${topicId}/${postNumber}`; return [ `**${topicTitle}**`, `Posted: ${date}`, `Topic: ${postUrl}`, categoryId ? `Category ID: ${categoryId}` : undefined, excerpt ? `\n${excerpt}${truncated}` : undefined, ].filter(Boolean).join("\n"); }); const totalShown = userActions.length; const pageInfo = page && page > 0 ? ` (page ${page})` : ""; const header = `Showing ${totalShown} posts for @${username}${pageInfo}:\n\n`; const footer = totalShown === 30 ? `\n\nTo see more posts, use page ${(page || 0) + 1}.` : ""; return { content: [{ type: "text", text: header + posts.join("\n\n---\n\n") + footer }] }; } catch (e: any) { return { content: [{ type: "text", text: `Failed to get posts for ${username}: ${e?.message || String(e)}` }], isError: true }; } } );
- src/tools/registry.ts:56-56 (registration)Top-level call to registerListUserPosts function within registerAllTools, which triggers the tool registration.registerListUserPosts(server, ctx, { allowWrites: false });