import { getSlackClient, handleSlackError } from "../slack-client.js";
import type { SlackMessage, SlackSearchMatch } from "../types/slack.js";
export async function getMessages(
channelId: string,
options?: {
limit?: number;
cursor?: string;
oldest?: string;
latest?: string;
inclusive?: boolean;
}
): Promise<{
messages: SlackMessage[];
nextCursor?: string;
hasMore: boolean;
}> {
const client = getSlackClient();
try {
const result = await client.conversations.history({
channel: channelId,
limit: options?.limit || 50,
cursor: options?.cursor,
oldest: options?.oldest,
latest: options?.latest,
inclusive: options?.inclusive,
});
const messages: SlackMessage[] = (result.messages || []).map((msg) => ({
type: msg.type || "message",
user: msg.user,
text: msg.text || "",
ts: msg.ts!,
thread_ts: msg.thread_ts,
reply_count: msg.reply_count,
reply_users_count: msg.reply_users_count,
reactions: msg.reactions?.map((r) => ({
name: r.name!,
count: r.count!,
users: r.users || [],
})),
files: msg.files?.map((f) => ({
id: f.id!,
name: f.name || "",
title: f.title,
mimetype: f.mimetype || "",
filetype: f.filetype || "",
size: f.size || 0,
url_private: f.url_private,
url_private_download: f.url_private_download,
permalink: f.permalink,
})),
}));
return {
messages,
nextCursor: result.response_metadata?.next_cursor || undefined,
hasMore: result.has_more || false,
};
} catch (error) {
handleSlackError(error);
}
}
export async function getThreadReplies(
channelId: string,
threadTs: string,
options?: {
limit?: number;
cursor?: string;
inclusive?: boolean;
}
): Promise<{
messages: SlackMessage[];
nextCursor?: string;
hasMore: boolean;
}> {
const client = getSlackClient();
try {
const result = await client.conversations.replies({
channel: channelId,
ts: threadTs,
limit: options?.limit || 50,
cursor: options?.cursor,
inclusive: options?.inclusive,
});
const messages: SlackMessage[] = (result.messages || []).map((msg) => ({
type: msg.type || "message",
user: msg.user,
text: msg.text || "",
ts: msg.ts!,
thread_ts: msg.thread_ts,
reactions: msg.reactions?.map((r) => ({
name: r.name!,
count: r.count!,
users: r.users || [],
})),
}));
return {
messages,
nextCursor: result.response_metadata?.next_cursor || undefined,
hasMore: result.has_more || false,
};
} catch (error) {
handleSlackError(error);
}
}
export async function sendMessage(
channelId: string,
text: string,
options?: {
threadTs?: string;
unfurlLinks?: boolean;
unfurlMedia?: boolean;
}
): Promise<{
ts: string;
channel: string;
message: SlackMessage;
}> {
const client = getSlackClient();
try {
const result = await client.chat.postMessage({
channel: channelId,
text,
thread_ts: options?.threadTs,
unfurl_links: options?.unfurlLinks ?? true,
unfurl_media: options?.unfurlMedia ?? true,
});
return {
ts: result.ts!,
channel: result.channel!,
message: {
type: "message",
user: result.message?.user,
text: result.message?.text || text,
ts: result.ts!,
thread_ts: options?.threadTs,
},
};
} catch (error) {
handleSlackError(error);
}
}
export async function searchMessages(
query: string,
options?: {
count?: number;
page?: number;
sortBy?: "score" | "timestamp";
sortDir?: "asc" | "desc";
}
): Promise<{
matches: SlackSearchMatch[];
total: number;
page: number;
pages: number;
}> {
const client = getSlackClient();
try {
const result = await client.search.messages({
query,
count: options?.count || 20,
page: options?.page || 1,
sort: options?.sortBy || "timestamp",
sort_dir: options?.sortDir || "desc",
});
const matches: SlackSearchMatch[] = (
result.messages?.matches || []
).map((m) => ({
type: m.type || "message",
user: m.user,
username: m.username,
text: m.text || "",
ts: m.ts!,
channel: {
id: m.channel?.id || "",
name: m.channel?.name || "",
},
permalink: m.permalink || "",
}));
return {
matches,
total: result.messages?.total || 0,
page: result.messages?.paging?.page || 1,
pages: result.messages?.paging?.pages || 1,
};
} catch (error) {
handleSlackError(error);
}
}
export const messageToolDefinitions = [
{
name: "slack_get_messages",
description: "Get messages from a Slack channel",
inputSchema: {
type: "object" as const,
properties: {
channel_id: {
type: "string",
description: "The ID of the channel (e.g., C1234567890)",
},
limit: {
type: "number",
description: "Maximum number of messages to return (max 1000)",
default: 50,
},
cursor: {
type: "string",
description: "Pagination cursor for next page",
},
oldest: {
type: "string",
description: "Only messages after this Unix timestamp",
},
latest: {
type: "string",
description: "Only messages before this Unix timestamp",
},
},
required: ["channel_id"],
},
},
{
name: "slack_get_thread_replies",
description: "Get replies in a message thread",
inputSchema: {
type: "object" as const,
properties: {
channel_id: {
type: "string",
description: "The ID of the channel containing the thread",
},
thread_ts: {
type: "string",
description: "Timestamp of the parent message (e.g., 1234567890.123456)",
},
limit: {
type: "number",
description: "Maximum number of replies to return",
default: 50,
},
cursor: {
type: "string",
description: "Pagination cursor for next page",
},
},
required: ["channel_id", "thread_ts"],
},
},
{
name: "slack_send_message",
description: "Send a message to a Slack channel",
inputSchema: {
type: "object" as const,
properties: {
channel_id: {
type: "string",
description: "The ID of the channel to send the message to",
},
text: {
type: "string",
description: "The message text to send",
},
thread_ts: {
type: "string",
description: "Optional: Reply to a thread by providing the parent message timestamp",
},
},
required: ["channel_id", "text"],
},
},
{
name: "slack_reply_to_thread",
description: "Reply to a message thread in Slack",
inputSchema: {
type: "object" as const,
properties: {
channel_id: {
type: "string",
description: "The ID of the channel containing the thread",
},
thread_ts: {
type: "string",
description: "Timestamp of the parent message to reply to",
},
text: {
type: "string",
description: "The reply text",
},
},
required: ["channel_id", "thread_ts", "text"],
},
},
{
name: "slack_search_messages",
description: "Search for messages across all channels (requires user token with search:read scope)",
inputSchema: {
type: "object" as const,
properties: {
query: {
type: "string",
description: "Search query (supports Slack search syntax: from:user, in:channel, has:reaction, etc.)",
},
count: {
type: "number",
description: "Number of results per page (max 100)",
default: 20,
},
page: {
type: "number",
description: "Page number",
default: 1,
},
sort_by: {
type: "string",
enum: ["score", "timestamp"],
description: "Sort results by relevance score or timestamp",
default: "timestamp",
},
sort_dir: {
type: "string",
enum: ["asc", "desc"],
description: "Sort direction",
default: "desc",
},
},
required: ["query"],
},
},
];