Skip to main content
Glama

mcp-open-library

index.ts4.91 kB
import { CallToolResult, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js"; import axios from "axios"; import { z } from "zod"; import { BookDetails, OpenLibraryBookResponse, OpenLibraryRecord, // Import the updated record type } from "./types.js"; // Schema for the get_book_by_id tool arguments const GetBookByIdArgsSchema = z.object({ idType: z .string() .transform((val) => val.toLowerCase()) .pipe( z.enum(["isbn", "lccn", "oclc", "olid"], { errorMap: () => ({ message: "idType must be one of: isbn, lccn, oclc, olid", }), }), ), idValue: z.string().min(1, { message: "idValue cannot be empty" }), }); // Type for the Axios instance type AxiosInstance = ReturnType<typeof axios.create>; export const handleGetBookById = async ( args: unknown, axiosInstance: AxiosInstance, ): Promise<CallToolResult> => { const parseResult = GetBookByIdArgsSchema.safeParse(args); if (!parseResult.success) { const errorMessages = parseResult.error.errors .map((e) => `${e.path.join(".")}: ${e.message}`) .join(", "); throw new McpError( ErrorCode.InvalidParams, `Invalid arguments for get_book_by_id: ${errorMessages}`, ); } const { idType, idValue } = parseResult.data; const apiUrl = `/api/volumes/brief/${idType}/${idValue}.json`; try { const response = await axiosInstance.get<OpenLibraryBookResponse>(apiUrl); // Check if records object exists and is not empty if ( !response.data || !response.data.records || Object.keys(response.data.records).length === 0 ) { return { content: [ { type: "text", text: `No book found for ${idType}: ${idValue}`, }, ], }; } // Get the first record from the records object const recordKey = Object.keys(response.data.records)[0]; const record: OpenLibraryRecord | undefined = response.data.records[recordKey]; if (!record) { // This case should theoretically not happen if the length check passed, but good for safety return { content: [ { type: "text", text: `Could not process book record for ${idType}: ${idValue}`, }, ], }; } const recordData = record.data; const recordDetails = record.details?.details; // Access the nested details const bookDetails: BookDetails = { title: recordData.title, subtitle: recordData.subtitle, authors: recordData.authors?.map((a) => a.name) || [], publishers: recordData.publishers?.map((p) => p.name), publish_date: recordData.publish_date, number_of_pages: recordData.number_of_pages ?? recordDetails?.number_of_pages, // Prefer identifiers from recordData, fallback to recordDetails if necessary isbn_13: recordData.identifiers?.isbn_13 ?? recordDetails?.isbn_13, isbn_10: recordData.identifiers?.isbn_10 ?? recordDetails?.isbn_10, lccn: recordData.identifiers?.lccn ?? recordDetails?.lccn, oclc: recordData.identifiers?.oclc ?? recordDetails?.oclc_numbers, olid: recordData.identifiers?.openlibrary, // Add OLID from identifiers open_library_edition_key: recordData.key, // From recordData open_library_work_key: recordDetails?.works?.[0]?.key, // From nested details cover_url: recordData.cover?.medium, // Use medium cover from recordData info_url: record.details?.info_url ?? recordData.url, // Prefer info_url from details preview_url: record.details?.preview_url ?? recordData.ebooks?.[0]?.preview_url, }; // Clean up undefined fields Object.keys(bookDetails).forEach((key) => { const typedKey = key as keyof BookDetails; if ( bookDetails[typedKey] === undefined || ((typedKey === "authors" || typedKey === "publishers") && Array.isArray(bookDetails[typedKey]) && bookDetails[typedKey].length === 0) ) { delete bookDetails[typedKey]; } }); return { content: [ { type: "text", text: JSON.stringify(bookDetails, null, 2), }, ], }; } catch (error) { let errorMessage = "Failed to fetch book data from Open Library."; if (axios.isAxiosError(error)) { if (error.response?.status === 404) { errorMessage = `No book found for ${idType}: ${idValue}`; } else { errorMessage = `API Error: ${error.response?.statusText ?? error.message}`; } } else if (error instanceof Error) { errorMessage = `Error processing request: ${error.message}`; } console.error("Error in get_book_by_id:", error); // Return error as text content return { content: [ { type: "text", text: errorMessage, }, ], }; } };

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/8enSmith/mcp-open-library'

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