import { z } from "zod";
import type { RegisterFn } from "../types.js";
let lastTopicAt = 0;
export const registerCreateTopic: RegisterFn = (server, ctx, opts) => {
if (!opts.allowWrites) return; // disabled by default
const schema = z.object({
title: z.string().min(1).max(300).describe("Topic title (1-300 characters). Should be descriptive and searchable"),
raw: z.string().min(1).max(30000).describe("First post content in Markdown format. Supports Discourse formatting, mentions (@user), and attachments"),
category_id: z.number().int().positive().optional().describe("Category ID to post in. Use discourse_list_categories to find available categories"),
tags: z.array(z.string().min(1).max(100)).max(10).optional().describe("Array of tag names (max 10). Use discourse_list_tags to find available tags"),
author_username: z.string().optional().describe("Create topic on behalf of this username (requires admin API key)"),
author_user_id: z.number().optional().describe("Create topic on behalf of this user ID (requires admin API key)"),
});
server.registerTool(
"discourse_create_topic",
{
title: "Create Topic",
description: "Create a new topic with the given title and first post. Requires write permissions. Rate limited to 1 request per second.",
inputSchema: schema.shape,
annotations: {
title: "Create Discourse Topic",
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
},
},
async (input: any, _extra: any) => {
const { title, raw, category_id, tags, author_username, author_user_id } = schema.parse(input);
// Simple 1 req/sec rate limit
const now = Date.now();
if (now - lastTopicAt < 1000) {
const wait = 1000 - (now - lastTopicAt);
await new Promise((r) => setTimeout(r, wait));
}
lastTopicAt = Date.now();
try {
const { base, client } = ctx.siteState.ensureSelectedSite();
const payload: any = { title, raw };
if (typeof category_id === "number") payload.category = category_id;
if (Array.isArray(tags) && tags.length > 0) payload.tags = tags;
if (author_username && author_username.length > 0) payload.author_username = author_username;
if (typeof author_user_id === "number") payload.author_user_id = author_user_id;
const data: any = await client.post(`/posts.json`, payload);
const topicId = data?.topic_id || data?.topicId || data?.topic?.id;
const slug = data?.topic_slug || data?.topic?.slug;
const postNumber = data?.post_number || data?.post?.post_number || 1;
const titleOut = data?.topic_title || data?.title || title;
const link = topicId
? slug
? `${base}/t/${slug}/${topicId}`
: `${base}/t/${topicId}/${postNumber}`
: `${base}/latest`;
return { content: [{ type: "text", text: `Created topic "${titleOut}": ${link}` }] };
} catch (e: any) {
return { content: [{ type: "text", text: `Failed to create topic: ${e?.message || String(e)}` }], isError: true };
}
}
);
};