From 6d4313aad3b35738769530163b539fb9b3338cd7 Mon Sep 17 00:00:00 2001 From: Casper Thomsen Date: Wed, 4 Mar 2026 15:56:23 +0100 Subject: [PATCH 1/2] feat: add pagination support to slack_get_thread_replies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Forward optional oldest, limit, and cursor parameters to the conversations.replies API endpoint, allowing callers to page through large threads that would otherwise exceed the MCP token limit. The raw response already includes response_metadata.next_cursor when more pages exist, so no additional changes are needed to surface it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- index.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/index.ts b/index.ts index 4687151..0fd9f90 100644 --- a/index.ts +++ b/index.ts @@ -174,11 +174,20 @@ export class SlackClient { return response.json(); } - async getThreadReplies(channel_id: string, thread_ts: string): Promise { + async getThreadReplies( + channel_id: string, + thread_ts: string, + oldest?: string, + limit?: number, + cursor?: string, + ): Promise { const params = new URLSearchParams({ channel: channel_id, ts: thread_ts, }); + if (oldest) params.set("oldest", oldest); + if (limit) params.set("limit", String(limit)); + if (cursor) params.set("cursor", cursor); const response = await fetch( `https://slack.com/api/conversations.replies?${params}`, @@ -327,10 +336,19 @@ export function createSlackServer(slackClient: SlackClient): McpServer { inputSchema: { channel_id: z.string().describe("The ID of the channel containing the thread"), thread_ts: z.string().describe("The timestamp of the parent message in the format '1234567890.123456'. Timestamps in the format without the period can be converted by adding the period such that 6 numbers come after it."), + oldest: z.string().optional().describe("Only return replies after this Unix timestamp. Use to fetch only new replies since a previous call."), + limit: z.number().optional().describe("Maximum number of replies to return per call"), + cursor: z.string().optional().describe("Pagination cursor from a previous response's next_cursor"), }, }, - async ({ channel_id, thread_ts }) => { - const response = await slackClient.getThreadReplies(channel_id, thread_ts); + async ({ channel_id, thread_ts, oldest, limit, cursor }) => { + const response = await slackClient.getThreadReplies( + channel_id, + thread_ts, + oldest, + limit, + cursor, + ); return { content: [{ type: "text", text: JSON.stringify(response) }], }; From 5299f2ef79d670af59aa5d30b11687e6dc9c3530 Mon Sep 17 00:00:00 2001 From: Casper Thomsen Date: Wed, 4 Mar 2026 16:42:04 +0100 Subject: [PATCH 2/2] test: add tests for getThreadReplies pagination params MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cover the two main use cases added in the previous commit: - oldest only (fetch replies since last known timestamp) - limit + cursor together (paginated continuation) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- tests/slack-mcp-server.test.ts | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/slack-mcp-server.test.ts b/tests/slack-mcp-server.test.ts index 43d448b..9cc0fae 100644 --- a/tests/slack-mcp-server.test.ts +++ b/tests/slack-mcp-server.test.ts @@ -302,6 +302,43 @@ describe('SlackClient', () => { ); }); + test('getThreadReplies with oldest only', async () => { + mockFetch.mockResolvedValueOnce({ + json: () => Promise.resolve({ ok: true, messages: [] }), + }); + + await slackClient.getThreadReplies('C123456', '1234567890.123456', '1234567891.000000'); + + const calledUrl: string = (mockFetch as any).mock.calls[0][0]; + expect(calledUrl).toContain('oldest=1234567891.000000'); + expect(calledUrl).not.toContain('limit='); + expect(calledUrl).not.toContain('cursor='); + }); + + test('getThreadReplies with limit and cursor for paginated continuation', async () => { + const mockResponse = { + ok: true, + messages: [ + { type: 'message', user: 'U789012', text: 'Reply', ts: '1234567892.000000' }, + ], + response_metadata: { next_cursor: 'page2' }, + }; + + mockFetch.mockResolvedValueOnce({ + json: () => Promise.resolve(mockResponse), + }); + + const result = await slackClient.getThreadReplies( + 'C123456', '1234567890.123456', undefined, 25, 'abc123' + ); + + const calledUrl: string = (mockFetch as any).mock.calls[0][0]; + expect(calledUrl).toContain('limit=25'); + expect(calledUrl).toContain('cursor=abc123'); + expect(calledUrl).not.toContain('oldest='); + expect(result.response_metadata.next_cursor).toBe('page2'); + }); + test('getUsers successful response', async () => { const mockResponse = { ok: true,