discourse_search
Search content across Discourse forums to find relevant topics, posts, and discussions using specific queries and filters.
Instructions
Search site content.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| max_results | No | ||
| query | Yes | Search query | |
| with_private | No |
Implementation Reference
- src/tools/builtin/search.ts:18-54 (handler)The handler function that executes the discourse_search tool. It queries the Discourse search API with the provided query (optionally prefixed), extracts top topics, formats a numbered list of results with URLs, appends a JSON summary, and returns as text content. Handles errors gracefully.async (args, _extra: any) => { const { query, with_private = false, max_results = 10 } = args; const { base, client } = ctx.siteState.ensureSelectedSite(); const q = new URLSearchParams(); q.set("expanded", "true"); const fullQuery = ctx.defaultSearchPrefix ? `${ctx.defaultSearchPrefix} ${query}` : query; q.set("q", fullQuery); try { const data = (await client.get(`/search.json?${q.toString()}`)) as any; const topics: any[] = data?.topics || []; const posts: any[] = data?.posts || []; const items = (topics.map((t) => ({ type: "topic" as const, id: t.id, title: t.title, slug: t.slug, })) as Array<{ type: "topic"; id: number; title: string; slug: string }>).slice(0, max_results); const lines: string[] = []; lines.push(`Top results for "${query}":`); let idx = 1; for (const it of items) { const url = `${base}/t/${it.slug}/${it.id}`; lines.push(`${idx}. ${it.title} – ${url}`); idx++; } const jsonFooter = { results: items.map((it) => ({ id: it.id, url: `${base}/t/${it.slug}/${it.id}`, title: it.title })), }; const text = lines.join("\n") + "\n\n```json\n" + JSON.stringify(jsonFooter) + "\n```\n"; return { content: [{ type: "text", text }] }; } catch (e: any) { return { content: [{ type: "text", text: `Search failed: ${e?.message || String(e)}` }], isError: true }; } }
- src/tools/builtin/search.ts:5-9 (schema)Zod input schema for discourse_search tool defining parameters: query (required string), with_private (optional boolean, unused in handler), max_results (optional int 1-50, defaults to 10).const schema = z.object({ query: z.string().min(1).describe("Search query"), with_private: z.boolean().optional(), max_results: z.number().int().min(1).max(50).optional(), });
- src/tools/builtin/search.ts:4-56 (registration)The registerSearch function that defines the schema, metadata, and registers the discourse_search tool handler with the MCP server.export const registerSearch: RegisterFn = (server, ctx) => { const schema = z.object({ query: z.string().min(1).describe("Search query"), with_private: z.boolean().optional(), max_results: z.number().int().min(1).max(50).optional(), }); server.registerTool( "discourse_search", { title: "Discourse Search", description: "Search site content.", inputSchema: schema.shape, }, async (args, _extra: any) => { const { query, with_private = false, max_results = 10 } = args; const { base, client } = ctx.siteState.ensureSelectedSite(); const q = new URLSearchParams(); q.set("expanded", "true"); const fullQuery = ctx.defaultSearchPrefix ? `${ctx.defaultSearchPrefix} ${query}` : query; q.set("q", fullQuery); try { const data = (await client.get(`/search.json?${q.toString()}`)) as any; const topics: any[] = data?.topics || []; const posts: any[] = data?.posts || []; const items = (topics.map((t) => ({ type: "topic" as const, id: t.id, title: t.title, slug: t.slug, })) as Array<{ type: "topic"; id: number; title: string; slug: string }>).slice(0, max_results); const lines: string[] = []; lines.push(`Top results for "${query}":`); let idx = 1; for (const it of items) { const url = `${base}/t/${it.slug}/${it.id}`; lines.push(`${idx}. ${it.title} – ${url}`); idx++; } const jsonFooter = { results: items.map((it) => ({ id: it.id, url: `${base}/t/${it.slug}/${it.id}`, title: it.title })), }; const text = lines.join("\n") + "\n\n```json\n" + JSON.stringify(jsonFooter) + "\n```\n"; return { content: [{ type: "text", text }] }; } catch (e: any) { return { content: [{ type: "text", text: `Search failed: ${e?.message || String(e)}` }], isError: true }; } } ); };
- src/tools/registry.ts:41-41 (registration)Top-level call to registerSearch within registerAllTools, enabling the discourse_search tool as part of the builtin tools suite.registerSearch(server, ctx, { allowWrites: false });