Barnsworthburning MCP

by Aias
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; const SEARCH_API_BASE = "https://barnsworthburning.net/api/search"; const USER_AGENT = "barnsworthburning-mcp/1.0"; const MAX_RESULTS = 25; export const SearchQuerySchema = z .string() .min(2, "Search query must be at least 2 characters") .describe("The search query to look for on barnsworthburning.net"); const LinkedRecordSchema = z.object({ id: z.string(), name: z.string(), }); const AttachmentSchema = z.object({ id: z.string(), url: z.string().url(), filename: z.string(), size: z.number().int().min(0).optional(), type: z.string(), width: z.number().int().min(0).optional(), height: z.number().int().min(0).optional(), }); export const SearchResultItemSchema = z.object({ id: z.string(), title: z.string().optional(), creators: z.array(LinkedRecordSchema).optional(), spaces: z.array(LinkedRecordSchema).optional(), connections: z.array(LinkedRecordSchema).optional(), parent: LinkedRecordSchema.optional(), parentCreators: z.array(LinkedRecordSchema).optional(), children: z.array(LinkedRecordSchema).optional(), extract: z.string().optional(), notes: z.string().optional(), images: z.array(AttachmentSchema).optional(), imageCaption: z.string().optional(), michelinStars: z.number().int().min(0).max(3).optional(), source: z.string().url().optional(), format: z.string().optional(), extractedOn: z.coerce.date(), lastUpdated: z.coerce.date(), publishedOn: z.coerce.date().optional(), }); export type SearchResultItem = z.infer<typeof SearchResultItemSchema>; export const SearchResultsSchema = z.object({ results: z.array(SearchResultItemSchema), }); export type SearchResults = z.infer<typeof SearchResultsSchema>; function formatResultItem(item: SearchResultItem) { const { title, id, creators, source, extract, format, spaces, connections, parent, children, notes, extractedOn, lastUpdated, } = item; let content = `## ${title ?? id}\n\n`; if (format) { content += `**Format:** ${format}\n`; } if (creators) { content += `**By:** ${creators.map((c) => c.name).join(", ")}\n`; } if (source) { content += `**Source:** ${source}\n`; } content += `**Created:** ${extractedOn.toLocaleDateString()}\n`; content += `**Updated:** ${lastUpdated.toLocaleDateString()}\n`; if (extract) { content += `\n${extract}\n`; } if (notes) { content += `\n*Curator's Note:*\n\n${notes}\n`; } content += `\n`; if (parent) { content += `**Parent Record:** ${parent.name}\n`; } if (children && children.length > 0) { content += `**Child Records:**\n${children .map((c) => `- ${c.name}`) .join("\n")}\n\n`; } if (connections && connections.length > 0) { content += `**See also:**\n${connections .map((c) => `- ${c.name}`) .join("\n")}\n\n`; } if (spaces && spaces.length > 0) { content += `**Tagged:** ${spaces.map((s) => `#${s.name}`).join(", ")}\n`; } return content; } // Create server instance const server = new McpServer({ name: "barnsworthburning-search", version: "1.0.0", }); // Helper function for making API requests async function makeSearchRequest( query: string ): Promise<SearchResultItem[] | null> { const headers = { "User-Agent": USER_AGENT, Accept: "application/json", }; try { const url = `${SEARCH_API_BASE}?q=${encodeURIComponent(query)}`; const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const json = await response.json(); const parsed = SearchResultsSchema.parse(json); return parsed.results; } catch (error) { console.error("Error making search request:", error); return null; } } // Register search tool server.tool( "search", "Search barnsworthburning.net for the given query", { query: SearchQuerySchema, }, async ({ query }) => { const searchData = await makeSearchRequest(query); if (!searchData) { return { content: [ { type: "text", text: "Failed to retrieve search results", }, ], }; } const results = searchData || []; if (results.length === 0) { return { content: [ { type: "text", text: `No results found for "${query}"`, }, ], }; } // Format the results as text const formattedResults = results .slice(0, MAX_RESULTS) .map(formatResultItem) .join("\n---\n\n"); return { content: [ { type: "text", text: `Search results for "${query}":\n\n${formattedResults}`, }, ], }; } ); // Run the server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Barnsworthburning Search MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });