Skip to main content
Glama

relentless_read

Retrieve specific entries from Notion databases using slugs to access complete content and properties for content management workflows.

Instructions

Read a specific entry from a Notion database by its slug. Returns the full entry with all properties and content.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
databaseYesThe database name (e.g., "blog", "docs", "leads")
slugYesThe slug of the entry to read (e.g., "getting-started", "hello-world")

Implementation Reference

  • The core handler function for the 'relentless_read' tool. Validates input parameters (database and slug), constructs the Relentless API endpoint, fetches the entry data using the shared relentlessRequest helper, and returns the full JSON response as text content.
    case 'relentless_read': { const { database, slug } = args as { database: string; slug: string } if (!database || !slug) { throw new McpError( ErrorCode.InvalidParams, 'Missing required parameters: database and slug' ) } console.error(`[${new Date().toISOString()}] Reading ${database}/${slug}`) const endpoint = `${RELENTLESS_API_BASE}/api/v1/public/db/${database}/read/${slug}` const result = await relentlessRequest(endpoint) return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], } }
  • src/index.ts:244-263 (registration)
    Tool registration in the ListTools response, defining the name, description, and input schema (parameters: database and slug). This makes the tool discoverable by MCP clients.
    { name: 'relentless_read', description: 'Read a specific entry from a Notion database by its slug. Returns the full entry with all properties and content.', inputSchema: { type: 'object', properties: { database: { type: 'string', description: 'The database name (e.g., "blog", "docs", "leads")', }, slug: { type: 'string', description: 'The slug of the entry to read (e.g., "getting-started", "hello-world")', }, }, required: ['database', 'slug'], }, },
  • Shared helper function used by relentless_read (and other tools) to make API requests to Relentless CMS with robust retry logic, rate limiting handling, timeouts, and detailed error messages.
    async function relentlessRequest( endpoint: string, options?: RequestInit, maxRetries = 3 ): Promise<any> { const url = endpoint.includes('?') ? `${endpoint}&api_key=${RELENTLESS_API_KEY}` : `${endpoint}?api_key=${RELENTLESS_API_KEY}` let lastError: Error | null = null for (let attempt = 0; attempt < maxRetries; attempt++) { try { // Add timeout using AbortController const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 30000) // 30s timeout const response = await fetch(url, { ...options, signal: controller.signal, headers: { 'Content-Type': 'application/json', ...options?.headers, }, }) clearTimeout(timeoutId) // Handle rate limiting (429) if (response.status === 429) { const retryAfter = response.headers.get('Retry-After') const delay = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, attempt) * 1000 console.error(`⏳ Rate limited. Retrying in ${delay / 1000}s...`) await sleep(delay) continue } // Handle success if (response.ok) { return response.json() } // Handle errors with detailed messages const errorBody = await response.json().catch(() => ({ message: 'Unknown error' })) let errorMessage = errorBody.message || errorBody.error || 'Unknown error' // Provide helpful error messages based on status code switch (response.status) { case 401: errorMessage = 'Invalid API key. Check RELENTLESS_API_KEY environment variable.' break case 403: errorMessage = 'Access forbidden. Verify you own this API and have proper permissions.' break case 404: errorMessage = `API path not found. Check that the API exists in your Relentless dashboard.` break case 422: errorMessage = `Validation error: ${errorMessage}. Check that property names match your Notion database exactly (case-sensitive).` break case 500: errorMessage = `Relentless API error: ${errorMessage}` break } // Don't retry client errors (4xx) except 429 if (response.status >= 400 && response.status < 500) { throw new McpError( ErrorCode.InvalidRequest, `Relentless API error (${response.status}): ${errorMessage}` ) } // Server errors (5xx) - will retry lastError = new Error(`Server error (${response.status}): ${errorMessage}`) console.error(`❌ Attempt ${attempt + 1}/${maxRetries} failed: ${errorMessage}`) } catch (error: any) { lastError = error // Handle timeout if (error.name === 'AbortError') { throw new McpError(ErrorCode.InternalError, 'Request timeout after 30 seconds') } // Handle MCP errors (don't retry) if (error instanceof McpError) { throw error } // Network errors - will retry console.error(`❌ Attempt ${attempt + 1}/${maxRetries} failed: ${error.message}`) } // Exponential backoff before retry (except on last attempt) if (attempt < maxRetries - 1) { const backoffDelay = Math.min(Math.pow(2, attempt) * 1000, 10000) // Max 10s console.error(`⏳ Retrying in ${backoffDelay / 1000}s...`) await sleep(backoffDelay) } } // All retries exhausted throw new McpError( ErrorCode.InternalError, `Failed after ${maxRetries} attempts: ${lastError?.message || 'Unknown error'}` ) }

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/PranaytheSingh/relentless-mcp'

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