Skip to main content
Glama
search-crates.ts•5.6 kB
import { z } from "zod" import { ErrorLogger, NetworkError } from "../errors.js" import type { DocsResponse } from "../types.js" // Crates.io API types interface CratesIoSearchResponse { crates: Array<{ name: string description: string | null downloads: number recent_downloads: number max_version: string documentation: string | null repository: string | null homepage: string | null }> meta: { total: number } } // Input schema for search_crates tool export const searchCratesInputSchema = { query: z.string().describe("Search query for crate names (supports partial matches)"), limit: z.number().optional().default(10).describe("Maximum number of results to return") } // Tool metadata export const searchCratesTool = { name: "search_crates", description: "Search for Rust crates on crates.io with fuzzy/partial name matching", annotations: { title: "Search Rust Crates", readOnlyHint: true, destructiveHint: true, idempotentHint: true, openWorldHint: true } } // Handler for search_crates export const createSearchCratesHandler = () => { return async ( args: z.infer<z.ZodObject<typeof searchCratesInputSchema>> ): Promise<DocsResponse> => { try { const searchUrl = `https://crates.io/api/v1/crates?q=${encodeURIComponent(args.query)}&per_page=${args.limit}` ErrorLogger.logInfo("Searching crates.io", { query: args.query, limit: args.limit }) // Add timeout to prevent hanging requests const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout const response = await fetch(searchUrl, { headers: { "User-Agent": "mcp-docsrs/1.0.0" }, signal: controller.signal }) clearTimeout(timeoutId) if (!response.ok) { throw new NetworkError(searchUrl, response.status, response.statusText) } const data = (await response.json()) as CratesIoSearchResponse if (data.crates.length === 0) { return { content: [ { type: "text", text: `No crates found matching "${args.query}"` } ] } } // Format results const results = data.crates .map((crate, index) => { const parts = [ `${index + 1}. **${crate.name}** v${crate.max_version}`, crate.description ? ` ${crate.description}` : "", ` Downloads: ${crate.downloads.toLocaleString()} (${crate.recent_downloads.toLocaleString()} recent)`, crate.documentation ? ` Docs: ${crate.documentation}` : "", "" ] return parts.filter((part) => part).join("\n") }) .join("\n") const header = `Found ${data.meta.total} crates matching "${args.query}" (showing top ${data.crates.length}):\n\n` return { content: [ { type: "text", text: header + results } ] } } catch (error) { // Log the error with full context if (error instanceof Error) { ErrorLogger.log(error) } let errorMessage: string if (error instanceof Error && error.name === "AbortError") { errorMessage = "Request timed out while searching crates.io" } else if (error instanceof NetworkError) { errorMessage = error.message } else if (error instanceof Error) { errorMessage = error.message } else { errorMessage = "Unknown error occurred" } return { content: [ { type: "text", text: `Error searching crates: ${errorMessage}` } ], isError: true } } } } // Helper function to suggest similar crate names export const suggestSimilarCrates = async (crateName: string, limit = 5): Promise<string[]> => { try { const searchUrl = `https://crates.io/api/v1/crates?q=${encodeURIComponent(crateName)}&per_page=${limit}` // Add timeout to prevent hanging requests const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout const response = await fetch(searchUrl, { headers: { "User-Agent": "mcp-docsrs/1.0.0" }, signal: controller.signal }) clearTimeout(timeoutId) if (!response.ok) { return [] } const data = (await response.json()) as CratesIoSearchResponse return data.crates.map((crate) => crate.name) } catch (error) { ErrorLogger.log(error as Error) return [] } } // Prompt arguments schema for search_crates export const searchCratesPromptSchema = { query: z.string().optional().describe("Search query for crate names (supports partial matches)"), limit: z.number().optional().describe("Maximum number of results to return") } // Prompt for search_crates with dynamic argument handling export const searchCratesPrompt = { name: "search_crates", description: "Search for Rust crates on crates.io", argsSchema: searchCratesPromptSchema, handler: (args: any) => { // Check if required arguments are missing if (!args?.query) { return { messages: [ { role: "user" as const, content: { type: "text" as const, text: "What would you like to search for on crates.io? Please provide a search query (e.g., 'serde', 'async', 'web framework')." } } ] } } // Build the prompt text with the provided arguments let promptText = `Search for Rust crates matching "${args.query}" on crates.io` if (args.limit) { promptText += ` (limiting to ${args.limit} results)` } promptText += `. I'll search for matching crates and show you the results.` return { messages: [ { role: "user" as const, content: { type: "text" as const, text: promptText } } ] } } }

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/vexxvakan/mcp-docsrs'

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