Skip to main content
Glama
search.ts3.97 kB
import { z } from "zod"; import type { RegisterFn } from "../types.js"; 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); // Debug logging ctx.logger?.debug(`Search query: "${query}"`); ctx.logger?.debug(`Full query with prefix: "${fullQuery}"`); ctx.logger?.debug(`Search URL: ${base}/search.json?${q.toString()}`); try { const data = (await client.get(`/search.json?${q.toString()}`)) as any; // Debug the response structure ctx.logger?.debug(`Search response keys: ${Object.keys(data || {}).join(', ')}`); ctx.logger?.debug(`Topics found: ${data?.topics?.length || 0}`); ctx.logger?.debug(`Posts found: ${data?.posts?.length || 0}`); const topics: any[] = data?.topics || []; const posts: any[] = data?.posts || []; // If no topics but we have posts, we can extract topic info from posts let items = topics.map((t) => ({ type: "topic" as const, id: t.id, title: t.title || t.fancy_title || `Topic ${t.id}`, slug: t.slug || String(t.id), })) as Array<{ type: "topic"; id: number; title: string; slug: string }>; // If we don't have enough topics, supplement with unique topics from posts if (items.length < max_results && posts.length > 0) { const existingTopicIds = new Set(items.map(t => t.id)); const postTopics = posts .filter(p => p.topic_id && !existingTopicIds.has(p.topic_id)) .map(p => ({ type: "topic" as const, id: p.topic_id, title: `Post topic ${p.topic_id}`, // We don't have the topic title from post slug: String(p.topic_id), })); // Add unique post topics up to our limit const seenIds = new Set(); for (const pt of postTopics) { if (items.length >= max_results) break; if (!seenIds.has(pt.id)) { seenIds.add(pt.id); items.push(pt); } } } items = items.slice(0, max_results); const lines: string[] = []; if (items.length === 0) { lines.push(`No results found for "${query}"`); // Add some debug info about what we got if (posts.length > 0) { lines.push(`Found ${posts.length} posts but no direct topics`); } } else { 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) { ctx.logger?.error(`Search failed: ${e?.message || String(e)}`); return { content: [{ type: "text", text: `Search failed: ${e?.message || String(e)}` }], isError: true }; } } ); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ahnmichael/gitlab-forum-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server