Skip to main content
Glama
SAhmadUmass

Notion MCP Server

by SAhmadUmass

add-article

Add articles to a Notion database by providing a URL and database ID, with the option to generate a summary. Simplify content organization and access within your Notion workspace.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
databaseIdYes
generateSummaryNo
urlYes

Implementation Reference

  • src/index.ts:747-748 (registration)
    Registration of the 'add-article' tool on the MCP server using server.tool(name, inputSchema, handler).
    "add-article",
    { 
  • Zod input schema validation for the add-article tool parameters.
    { 
      url: z.string().url(),
      databaseId: z.string(),
      generateSummary: z.boolean().default(true)
    },
  • Main execution handler for add-article tool. Fetches article metadata from URL, auto-maps to Notion database properties, creates new database entry, generates summary if requested, appends article content as blocks.
      async ({ url, databaseId, generateSummary }) => {
        try {
          // First retrieve database to get property types
          const databaseInfo = await notion.databases.retrieve({
            database_id: databaseId
          });
          
          // Get all available property types
          const propertyInfoMap = databaseInfo.properties || {};
          
          // Auto-detect property names
          const urlPropertyName = findMatchingProperty(propertyInfoMap, [
            "URL", "Link", "Website", "Address", "Source Link"
          ]);
          
          const titlePropertyName = findMatchingProperty(propertyInfoMap, [
            "Title", "Name", "Article Title", "Headline", "Topic"
          ]);
          
          const publicationPropertyName = findMatchingProperty(propertyInfoMap, [
            "Publication", "Publisher", "Source", "Site", "Website Name", "Origin"
          ]);
          
          const authorPropertyName = findMatchingProperty(propertyInfoMap, [
            "Author", "Author(s)", "Writer", "Creator", "By"
          ]);
          
          const datePropertyName = findMatchingProperty(propertyInfoMap, [
            "Date", "Published", "Published Date", "Publish Date", "Release Date", "Post Date"
          ]);
          
          const summaryPropertyName = findMatchingProperty(propertyInfoMap, [
            "Summary", "Article Summary", "TLDR", "Description", "Brief"
          ]);
          
          // Get property types for the detected properties
          const titlePropertyType = getPropertyType(propertyInfoMap, titlePropertyName);
          const publicationPropertyType = getPropertyType(propertyInfoMap, publicationPropertyName);
          const authorPropertyType = getPropertyType(propertyInfoMap, authorPropertyName);
          const datePropertyType = getPropertyType(propertyInfoMap, datePropertyName);
          const summaryPropertyType = getPropertyType(propertyInfoMap, summaryPropertyName);
          const urlPropertyType = getPropertyType(propertyInfoMap, urlPropertyName);
          
          // Log the detected fields
          console.log(`Using field mapping:
    - Title: "${titlePropertyName}" (${titlePropertyType})
    - URL: "${urlPropertyName}" (${urlPropertyType})
    - Publication: "${publicationPropertyName}" (${publicationPropertyType})
    - Author: "${authorPropertyName}" (${authorPropertyType})
    - Date: "${datePropertyName}" (${datePropertyType})
    - Summary: "${summaryPropertyName}" (${summaryPropertyType})`);
          
          // Extract metadata from the URL
          const metadata = await extractMetadataFromUrl(url);
          const { publication, author, date, content } = metadata;
          
          // Use the URL's title or domain as the article title if not extracted
          let title = "";
          
          // Try to extract title from HTML
          try {
            const response = await axios.get(url, {
              headers: {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
              },
              timeout: 10000,
              maxRedirects: 5
            });
            
            const $ = cheerio.load(response.data);
            title = $('title').text().trim() || 
                    $('meta[property="og:title"]').attr('content') || 
                    $('meta[name="twitter:title"]').attr('content') || 
                    new URL(url).hostname;
          } catch (error) {
            // If we can't access the URL, use the domain as title
            try {
              title = new URL(url).hostname;
            } catch (e) {
              title = url;
            }
          }
          
          // Generate summary if needed
          let summary = "";
          if (generateSummary && content) {
            // For now, use a simple summarization method
            summary = createSimpleSummary(content);
          }
          
          // Create the page properties
          const properties: any = {};
          
          // Set the title property
          if (titlePropertyName && titlePropertyType) {
            if (titlePropertyType === 'title') {
              properties[titlePropertyName] = createTitleProperty(title);
            } else if (titlePropertyType === 'rich_text') {
              properties[titlePropertyName] = createRichTextProperty(title);
            }
          }
          
          // Set the URL property
          if (urlPropertyName && urlPropertyType) {
            if (urlPropertyType === 'url') {
              properties[urlPropertyName] = { url };
            } else if (urlPropertyType === 'rich_text') {
              properties[urlPropertyName] = createRichTextProperty(url);
            }
          }
          
          // Set the publication property
          if (publicationPropertyName && publicationPropertyType && publication) {
            if (publicationPropertyType === 'select') {
              properties[publicationPropertyName] = createSelectProperty(publication);
            } else if (publicationPropertyType === 'rich_text') {
              properties[publicationPropertyName] = createRichTextProperty(publication);
            } else if (publicationPropertyType === 'title') {
              properties[publicationPropertyName] = createTitleProperty(publication);
            }
          }
          
          // Set the author property
          if (authorPropertyName && authorPropertyType && author) {
            if (authorPropertyType === 'multi_select') {
              properties[authorPropertyName] = createMultiSelectProperty(parseAuthors(author));
            } else if (authorPropertyType === 'select') {
              properties[authorPropertyName] = createSelectProperty(author);
            } else if (authorPropertyType === 'rich_text') {
              properties[authorPropertyName] = createRichTextProperty(author);
            }
          }
          
          // Set the date property
          if (datePropertyName && datePropertyType === 'date' && date) {
            properties[datePropertyName] = createDateProperty(date);
          }
          
          // Set the summary property
          if (summaryPropertyName && summaryPropertyType && summary) {
            if (summaryPropertyType === 'rich_text') {
              properties[summaryPropertyName] = createRichTextProperty(summary);
            } else if (summaryPropertyType === 'select') {
              properties[summaryPropertyName] = createSelectProperty(summary);
            } else if (summaryPropertyType === 'multi_select') {
              properties[summaryPropertyName] = createMultiSelectProperty([summary]);
            }
          }
          
          // Create the page in Notion
          const response = await notion.pages.create({
            parent: {
              database_id: databaseId
            },
            properties: properties
          });
          
          // Add content blocks if we have content
          if (content && response.id) {
            try {
              // Create content blocks (paragraphs)
              const contentBlocks = createContentBlocks(content);
              
              await notion.blocks.children.append({
                block_id: response.id,
                children: contentBlocks
              });
            } catch (err: any) {
              console.error(`Error updating content: ${err.message}`);
            }
          }
          
          // Return success with extracted fields
          return {
            content: [{
              type: "text",
              text: `βœ… Article added to your database!\n\n` +
                    `πŸ”— URL: ${url}\n` +
                    `πŸ“ Title: ${title}\n` +
                    (publication ? `πŸ“° Publication: ${publication}\n` : '') +
                    (author ? `✍️ Author: ${author}\n` : '') +
                    (date ? `πŸ“… Date: ${date}\n` : '') +
                    (summary ? `\nπŸ“Œ Summary: ${summary}` : '')
            }]
          };
        } catch (error: any) {
          return {
            content: [{
              type: "text",
              text: `Error adding article: ${error.message}`
            }],
            isError: true
          };
        }
      }
  • Helper function to automatically detect relevant Notion database property names (e.g., URL, Title, Author) by exact, case-insensitive, and partial matching.
    function findMatchingProperty(propertyInfoMap: any, possibleNames: string[]): string {
      const availableProperties = Object.keys(propertyInfoMap);
      
      // First try exact match
      for (const name of possibleNames) {
        if (availableProperties.includes(name)) {
          return name;
        }
      }
      
      // Then try case-insensitive match
      for (const name of possibleNames) {
        const match = availableProperties.find(prop => 
          prop.toLowerCase() === name.toLowerCase()
        );
        if (match) {
          return match;
        }
      }
      
      // Then try partial match (contains)
      for (const name of possibleNames) {
        const match = availableProperties.find(prop => 
          prop.toLowerCase().includes(name.toLowerCase()) || 
          name.toLowerCase().includes(prop.toLowerCase())
        );
        if (match) {
          return match;
        }
      }
      
      // Default to the first possible name if no match found
      return possibleNames[0];
    }
  • Key helper to scrape article metadata from URL using axios and cheerio, extracting publication, author, date, and main content via specialized sub-helpers.
    async function extractMetadataFromUrl(url: string) {
      // Fetch the webpage
      const response = await axios.get(url, {
        headers: {
          'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
          'Accept': 'text/html,application/xhtml+xml,application/xml',
          'Accept-Language': 'en-US,en;q=0.9'
        },
        timeout: 10000,
        maxRedirects: 5
      });
      
      // Parse HTML
      const $ = cheerio.load(response.data);
      
      // Extract metadata
      const publication = extractPublication($, url);
      const author = extractAuthor($);
      const date = extractDate($);
      const content = extractContent($);
      
      return { publication, author, date, content };
    }
Install Server

Other Tools

Related Tools

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/SAhmadUmass/notion-mcp-server'

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