WORLDBOOK

Worldbooks | WebMCP | Search | Submit WebMCP

producthunt WebMCP

Browser tool configuration for producthunt

URL Pattern: ^https?://(www\.)?producthunt\.com(/.*)?$
Allowed Extra Domains: producthunt.com

Tools (1)

producthunt_today()

Product Hunt 今日热门产品

Parameters

count string - Number of products to return (default: 20, max: 50)

JavaScript Handler

(params) => {
  const run = async function(args) {

      const count = Math.min(parseInt(args.count) || 20, 50);

      // Strategy 1: Try frontend GraphQL API (works when browsing producthunt.com)
      try {
        const today = new Date();
        const dateStr = today.getFullYear() + '-' +
          String(today.getMonth() + 1).padStart(2, '0') + '-' +
          String(today.getDate()).padStart(2, '0');

        // Extract CSRF token from meta tag or cookie
        const csrfMeta = document.querySelector('meta[name="csrf-token"]');
        const csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : '';

        const headers = {
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        };
        if (csrfToken) headers['X-CSRF-Token'] = csrfToken;

        const query = `query HomefeedQuery($date: DateTime, $cursor: String) {
          homefeed(date: $date, after: $cursor, first: 50) {
            edges {
              node {
                ... on Post {
                  id
                  name
                  tagline
                  description
                  votesCount
                  commentsCount
                  createdAt
                  featuredAt
                  slug
                  url
                  website
                  reviewsRating
                  thumbnailUrl
                  topics(first: 5) {
                    edges {
                      node {
                        name
                        slug
                      }
                    }
                  }
                  makers {
                    name
                    username
                  }
                }
              }
            }
          }
        }`;

        const gqlResp = await fetch('/frontend/graphql', {
          method: 'POST',
          headers,
          credentials: 'include',
          body: JSON.stringify({
            query,
            variables: { date: dateStr + 'T00:00:00Z', cursor: null }
          })
        });

        if (gqlResp.ok) {
          const gqlData = await gqlResp.json();
          const edges = gqlData?.data?.homefeed?.edges;
          if (edges && edges.length > 0) {
            const products = edges
              .map(e => e.node)
              .filter(n => n && n.name)
              .slice(0, count)
              .map((p, i) => ({
                rank: i + 1,
                id: p.id,
                name: p.name,
                tagline: p.tagline || '',
                description: (p.description || '').substring(0, 300),
                votes: p.votesCount || 0,
                comments: p.commentsCount || 0,
                url: p.url || ('https://www.producthunt.com/posts/' + p.slug),
                website: p.website || '',
                rating: p.reviewsRating || null,
                thumbnail: p.thumbnailUrl || '',
                topics: (p.topics?.edges || []).map(t => t.node?.name).filter(Boolean),
                makers: (p.makers || []).map(m => m.name || m.username).filter(Boolean),
                featured_at: p.featuredAt || p.createdAt || ''
              }));
            return { source: 'graphql', date: dateStr, count: products.length, products };
          }
        }
      } catch (e) {
        // GraphQL failed, try next strategy
      }

      // Strategy 2: Try extracting __NEXT_DATA__ from the page (SSR)
      try {
        const nextDataEl = document.querySelector('#__NEXT_DATA__');
        if (nextDataEl) {
          const nextData = JSON.parse(nextDataEl.textContent);
          // Navigate Next.js data structure to find posts
          const pageProps = nextData?.props?.pageProps;
          // Try multiple possible data paths
          const posts = pageProps?.posts || pageProps?.homefeed?.edges?.map(e => e.node) ||
            pageProps?.data?.homefeed?.edges?.map(e => e.node) || [];
          if (posts.length > 0) {
            const products = posts.slice(0, count).map((p, i) => ({
              rank: i + 1,
              id: p.id,
              name: p.name,
              tagline: p.tagline || '',
              description: (p.description || '').substring(0, 300),
              votes: p.votesCount || p.votes_count || 0,
              comments: p.commentsCount || p.comments_count || 0,
              url: p.url || ('https://www.producthunt.com/posts/' + (p.slug || '')),
              website: p.website || '',
              topics: (p.topics || []).map(t => typeof t === 'string' ? t : (t.name || t.slug || '')).filter(Boolean),
              makers: (p.makers || []).map(m => m.name || m.username || '').filter(Boolean),
              featured_at: p.featuredAt || p.featured_at || ''
            }));
            return { source: 'nextdata', count: products.length, products };
          }
        }
      } catch (e) {
        // __NEXT_DATA__ extraction failed, try next strategy
      }

      // Strategy 3: Try Apollo cache (Product Hunt uses Apollo Client)
      try {
        const apolloState = window.__APOLLO_STATE__ || window.__APOLLO_CLIENT__?.cache?.data?.data;
        if (apolloState) {
          const postKeys = Object.keys(apolloState).filter(k => k.startsWith('Post:'));
          if (postKeys.length > 0) {
            const products = postKeys
              .map(k => apolloState[k])
              .filter(p => p && p.name)
              .sort((a, b) => (b.votesCount || 0) - (a.votesCount || 0))
              .slice(0, count)
              .map((p, i) => ({
                rank: i + 1,
                id: p.id,
                name: p.name,
                tagline: p.tagline || '',
                votes: p.votesCount || 0,
                comments: p.commentsCount || 0,
                url: p.url || ('https://www.producthunt.com/posts/' + (p.slug || '')),
                website: p.website || '',
                topics: [],
                makers: []
              }));
            return { source: 'apollo_cache', count: products.length, products };
          }
        }
      } catch (e) {
        // Apollo cache extraction failed, try next strategy
      }

      // Strategy 4: Parse the DOM directly
      try {
        // Navigate to homepage if not already there
        const sections = document.querySelectorAll('[data-test="homepage-section"], [class*="styles_item"], [data-test="post-item"]');
        if (sections.length === 0) {
          // Try broader selectors for product cards
          const allLinks = document.querySelectorAll('a[href*="/posts/"]');
          const seen = new Set();
          const products = [];
          for (const link of allLinks) {
            const href = link.getAttribute('href');
            if (!href || seen.has(href)) continue;
            seen.add(href);
            // Find the closest parent card element
            const card = link.closest('[class*="item"], [class*="post"], li, article') || link.parentElement;
            if (!card) continue;
            const name = card.querySelector('h3, [class*="title"], [class*="name"]')?.textContent?.trim() ||
              link.textContent?.trim() || '';
            if (!name || name.length > 100) continue;
            const tagline = card.querySelector('[class*="tagline"], [class*="description"], p')?.textContent?.trim() || '';
            // Try to find vote count
            const voteEl = card.querySelector('[class*="vote"], [class*="count"], button');
            const voteText = voteEl?.textContent?.trim() || '';
            const votes = parseInt(voteText.replace(/[^\d]/g, '')) || 0;

            products.push({
              rank: products.length + 1,
              name,
              tagline: tagline.substring(0, 200),
              votes,
              url: href.startsWith('http') ? href : 'https://www.producthunt.com' + href,
              topics: [],
              makers: []
            });
            if (products.length >= count) break;
          }
          if (products.length > 0) {
            return { source: 'dom_parse', count: products.length, products };
          }
        } else {
          const products = [];
          for (const section of sections) {
            const name = section.querySelector('h3, [class*="title"]')?.textContent?.trim() || '';
            const tagline = section.querySelector('[class*="tagline"], p')?.textContent?.trim() || '';
            const voteEl = section.querySelector('[class*="vote"], button');
            const votes = parseInt((voteEl?.textContent || '').replace(/[^\d]/g, '')) || 0;
            const link = section.querySelector('a[href*="/posts/"]');
            const href = link?.getAttribute('href') || '';
            if (!name) continue;
            products.push({
              rank: products.length + 1,
              name,
              tagline: tagline.substring(0, 200),
              votes,
              url: href.startsWith('http') ? href : 'https://www.producthunt.com' + href,
              topics: [],
              makers: []
            });
            if (products.length >= count) break;
          }
          if (products.length > 0) {
            return { source: 'dom_parse', count: products.length, products };
          }
        }
      } catch (e) {
        // DOM parsing failed
      }

      // Strategy 5: Fallback to Atom feed (no vote counts, but always works)
      try {
        const feedResp = await fetch('https://www.producthunt.com/feed', {credentials: 'include'});
        if (feedResp.ok) {
          const feedText = await feedResp.text();
          const parser = new DOMParser();
          const xmlDoc = parser.parseFromString(feedText, 'application/xml');
          const entries = xmlDoc.querySelectorAll('entry');
          const products = [];
          for (const entry of entries) {
            const title = entry.querySelector('title')?.textContent?.trim() || '';
            const content = entry.querySelector('content')?.textContent?.trim() || '';
            const link = entry.querySelector('link[rel="alternate"]')?.getAttribute('href') || '';
            const author = entry.querySelector('author name')?.textContent?.trim() || '';
            const published = entry.querySelector('published')?.textContent?.trim() || '';
            const id = entry.querySelector('id')?.textContent?.trim() || '';
            const postId = id.match(/Post\/(\d+)/)?.[1] || '';
            if (!title) continue;
            // Strip HTML tags from content
            const tagline = content.replace(/<[^>]*>/g, '').trim();
            products.push({
              rank: products.length + 1,
              id: postId,
              name: title,
              tagline: tagline.substring(0, 200),
              author,
              url: link,
              published,
              votes: null,
              topics: [],
              makers: [author].filter(Boolean)
            });
            if (products.length >= count) break;
          }
          if (products.length > 0) {
            return {
              source: 'atom_feed',
              note: 'Vote counts unavailable via feed. Open producthunt.com first for richer data.',
              count: products.length,
              products
            };
          }
        }
      } catch (e) {
        // Feed also failed
      }

      return {
        error: 'Could not fetch Product Hunt data',
        hint: 'Open https://www.producthunt.com in bb-browser first, then retry. The adapter needs browser context with cookies.'
      };
  };
  return run(params || {});
}

🔌 Chrome MCP Server Extension

Use these tools with Claude, ChatGPT, and other AI assistants.

Get Extension →

How to Use WebMCP

WebMCP tools are designed for browser extensions or automation frameworks. The browser extension matches the current URL against the pattern and executes the JavaScript handler when the tool is invoked.

API Endpoint:

GET /api/webmcp/match?url=https://www.producthunt.com/...