Skip to main content
Glama
WileySearcher.ts5.98 kB
/** * Wiley TDM (Text and Data Mining) API - PDF Download Only * * Documentation: https://onlinelibrary.wiley.com/library-info/resources/text-and-datamining * GitHub Client: https://github.com/WileyLabs/tdm-client * * IMPORTANT: Wiley TDM API does NOT support keyword search. * It only supports downloading PDFs by DOI. * For searching Wiley content, use Crossref API with publisher filter. * * API Endpoint: https://api.wiley.com/onlinelibrary/tdm/v1/articles/{DOI} * Header: Wiley-TDM-Client-Token: <token> * * Rate limits: * - Up to 3 articles per second * - Up to 60 requests per 10 minutes (build in 10 second delay between requests) */ import axios, { AxiosInstance } from 'axios'; import { PaperSource, SearchOptions, DownloadOptions, PlatformCapabilities } from './PaperSource.js'; import { Paper, PaperFactory } from '../models/Paper.js'; import { RateLimiter } from '../utils/RateLimiter.js'; import { sanitizeDoi } from '../utils/SecurityUtils.js'; import { TIMEOUTS } from '../config/constants.js'; export class WileySearcher extends PaperSource { private client: AxiosInstance; private rateLimiter: RateLimiter; constructor(tdmToken?: string) { super('wiley', 'https://api.wiley.com/onlinelibrary/tdm/v1', tdmToken); this.client = axios.create({ baseURL: 'https://api.wiley.com/onlinelibrary/tdm/v1', headers: { 'Accept': 'application/pdf', ...(tdmToken ? { 'Wiley-TDM-Client-Token': tdmToken } : {}) }, maxRedirects: 5, timeout: TIMEOUTS.EXTENDED }); // Wiley rate limits: 3 articles/sec, 60 requests/10min this.rateLimiter = new RateLimiter({ requestsPerSecond: 0.1, // Conservative: ~6 per minute burstCapacity: 3 }); } /** * Search is NOT supported by Wiley TDM API. * Use Crossref API to search for Wiley articles, then use download() to get PDFs. */ async search(query: string, options: SearchOptions = {}): Promise<Paper[]> { throw new Error( 'Wiley TDM API does not support keyword search. ' + 'Use Crossref API (search_crossref) to find Wiley articles by DOI, ' + 'then use download_paper with platform="wiley" to download PDFs.' ); } /** * Download PDF by DOI using Wiley TDM API * @param doi - The DOI of the article (e.g., "10.1111/jtsb.12390") * @param options - Download options including savePath */ async downloadPdf(doi: string, options: { savePath?: string } = {}): Promise<string> { if (!this.apiKey) { throw new Error('Wiley TDM token is required. Set WILEY_TDM_TOKEN environment variable.'); } // Clean and validate DOI format const doiResult = sanitizeDoi(doi); if (!doiResult.valid) { throw new Error(`Invalid DOI format: ${doi}. ${doiResult.error || ''}`); } const cleanDoi = doiResult.sanitized; const fs = await import('fs'); const path = await import('path'); const savePath = options.savePath || './downloads'; if (!fs.existsSync(savePath)) { fs.mkdirSync(savePath, { recursive: true }); } // Encode DOI for URL (replace / with %2F) const encodedDoi = encodeURIComponent(cleanDoi); const url = `/articles/${encodedDoi}`; await this.rateLimiter.waitForPermission(); try { const response = await this.client.get(url, { responseType: 'stream', headers: { 'Wiley-TDM-Client-Token': this.apiKey, 'Accept': 'application/pdf' } }); // Generate filename from DOI const fileName = `${cleanDoi.replace(/[\/\\:*?"<>|]/g, '_')}.pdf`; const filePath = path.join(savePath, fileName); const writer = fs.createWriteStream(filePath); response.data.pipe(writer); return new Promise((resolve, reject) => { writer.on('finish', () => resolve(filePath)); writer.on('error', reject); }); } catch (error: any) { const status = error.response?.status; const errorMessages: Record<number, string> = { 400: 'No TDM Client Token was found in the request', 403: 'TDM Client Token is invalid or not registered', 404: 'Access denied - you or your institution does not have access to this content. Check your subscription.', 429: 'Rate limit exceeded. Please reduce request frequency (max 60 requests per 10 minutes).' }; if (status && errorMessages[status]) { throw new Error(`Wiley TDM Error (${status}): ${errorMessages[status]}`); } throw new Error(`Failed to download PDF: ${error.message}`); } } /** * Get article metadata and download link (without downloading) */ async getArticleInfo(doi: string): Promise<Paper> { // Clean and validate DOI const doiResult = sanitizeDoi(doi); const cleanDoi = doiResult.valid ? doiResult.sanitized : doi; // Since TDM API only provides PDF download, we create basic paper info from DOI return PaperFactory.create({ paperId: cleanDoi, title: `Wiley Article: ${cleanDoi}`, authors: [], abstract: '', doi: cleanDoi, publishedDate: null, pdfUrl: `https://api.wiley.com/onlinelibrary/tdm/v1/articles/${encodeURIComponent(cleanDoi)}`, url: `https://doi.org/${cleanDoi}`, source: 'wiley', extra: { note: 'Use Crossref API for full metadata. This endpoint only provides PDF download.' } }); } getCapabilities(): PlatformCapabilities { return { search: false, // TDM API does not support search download: true, // PDF download by DOI fullText: true, // Full PDF available citations: false, requiresApiKey: true, supportedOptions: [] // No search options - only DOI-based download }; } async readPaper(paperId: string, options: DownloadOptions = {}): Promise<string> { return 'Wiley TDM API only supports PDF download. Use downloadPdf() method with a DOI to get the full PDF.'; } }

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/Dianel555/paper-search-mcp-nodejs'

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