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 };
    }
Behavior1/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Tool has no description.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness1/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Tool has no description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness1/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Tool has no description.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Tool has no description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose1/5

Does the description clearly state what the tool does and how it differs from similar tools?

Tool has no description.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines1/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Tool has no description.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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