DEV Community

Cover image for I built an AI E-Commerce Brain with Notion MCP
Enrique Uribe
Enrique Uribe Subscriber

Posted on

I built an AI E-Commerce Brain with Notion MCP

Notion MCP Challenge Submission 🧠

This is a submission for the Notion MCP Challenge

What I Built

I built an AI e-commerce brain for a Shopify storefront using Notion MCP as the behavior control layer.

On the surface, it looks like one shopping assistant. Under the hood, it behaves more like a network of specialists:

  • a training bag specialist
  • a gloves and wraps specialist
  • a cart review specialist
  • an order history specialist
  • an add-to-cart specialist

The architecture is split intentionally.

The app keeps ownership of:

  • intent classification and routing
  • Shopify integration
  • ranking and filtering
  • session and conversation state
  • validation and fallback logic
  • performance and caching

Notion owns the behavior layer for each intent:

  • tone and voice
  • intent instructions
  • recommendation strategy
  • questions to ask
  • do / do not rules
  • example responses
  • behavior notes
  • synced intent synonyms

That means I can change how the AI behaves per scenario without rewriting prompts throughout the codebase.

Instead of one generic chatbot, I now have a commerce system made up of specialist branches like:

  • search_training_bags
  • search_gloves_wraps
  • cart_review
  • orders_selection
  • order_detail

This makes the behavior visible, editable, and operational.

preview of notion dashboard

I also built a publish model so Notion is safe to use in production:

  1. Edit behavior in Notion
  2. Click Sync from Notion
  3. Publish the behavior into the app
  4. Run the storefront from the published config locally for fast runtime performance

I also built the reverse flow:

  • Sync to Notion

So the app can push its current behavior definitions back into the Notion database.

This turned Notion from a documentation layer into a real control plane for the AI.

notion sync flow

Video Demo

In the demo, I walk through:

  • the Notion behavior control panel
  • the specialist intent rows
  • the sync workflow
  • changing behavior in Notion and seeing the storefront assistant respond differently
  • category-specific shopping flows like training bags and gloves/wraps

Show us the code

Parse Notion rows into behavior config

This is the layer that turns a Notion database row into a specialist behavior object the app can use at runtime.

function parseNotionBehaviorRow(page) {
  const properties = page?.properties || {};
  const intent = pickProperty(properties, ['intent', 'name', 'title']);
  const key = normalizeIntentKey(intent);
  if (!key) return null;

  const enabledRaw = pickProperty(properties, ['enabled', 'active']);
  const enabled = enabledRaw ? enabledRaw === 'true' : true;

  return {
    key,
    enabled,
    intentSynonyms: pickProperty(properties, ['intent_synonyms', 'synonyms', 'trigger_phrases']),
    toneVoice: pickProperty(properties, ['tone_voice', 'tone', 'voice']),
    intentInstructions: pickProperty(properties, ['intent_instructions', 'instructions']),
    questionsToAsk: pickProperty(properties, ['questions_to_ask', 'questions']),
    recommendationStrategy: pickProperty(properties, ['product_recommendation_strategy', 'recommendation_strategy']),
    doRules: pickProperty(properties, ['do', 'do_rules']),
    doNotRules: pickProperty(properties, ['do_not', 'do_not_rules']),
    upsellCrossSellStrategy: pickProperty(properties, ['upsell_cross_sell_strategy', 'upsell_strategy', 'cross_sell_strategy']),
    exampleResponses: pickProperty(properties, ['example_responses', 'examples']),
    behaviorNotes: pickProperty(properties, ['behavior_notes', 'notes']),
  };
}

Enter fullscreen mode Exit fullscreen mode

Sync behavior from Notion into the app

This is the publish step. It pulls the latest behavior definitions from Notion and stores a published config in the app for fast local runtime use.

export async function syncChatBehaviorFromNotion(shop) {
  const config = await fetchNotionBehaviorDatabase();
  const publishedConfig = normalizePublishedBehaviorConfig({
    ...config,
    source: 'published_notion',
    publishedAt: new Date().toISOString(),
  });

  await updateSettings(shop, {
    behaviorConfigJson: JSON.stringify(publishedConfig),
    behaviorConfigSource: publishedConfig.source,
    behaviorSyncedAt: new Date(),
  });

  behaviorCache = {
    shop,
    config: publishedConfig,
    expiresAt: Date.now() + (Number(process.env.NOTION_BEHAVIOR_CACHE_MS || DEFAULT_CACHE_MS) || DEFAULT_CACHE_MS),
  };

  return publishedConfig;
}

Enter fullscreen mode Exit fullscreen mode

Sync behavior back to Notion

This is the reverse direction. It pushes app-side behavior definitions back into the Notion control panel so the system stays visible and editable.

export async function syncChatBehaviorToNotion(shop) {
  const token = sanitizeText(process.env.NOTION_API_TOKEN);
  const databaseId = sanitizeText(process.env.NOTION_BEHAVIOR_DATABASE_ID);
  if (!token || !databaseId) {
    throw new Error('Missing NOTION_API_TOKEN or NOTION_BEHAVIOR_DATABASE_ID.');
  }

  const settings = shop ? await getSettings(shop) : null;
  const publishedConfig = parseBehaviorConfigJson(settings?.behaviorConfigJson) || cloneDefaultConfig();
  const effectiveConfig = normalizePublishedBehaviorConfig({
    ...publishedConfig,
    source: publishedConfig?.source || 'published',
  });

  const existingRows = await queryNotionBehaviorRows(token, databaseId);
  const rowMap = new Map();

  for (const row of existingRows) {
    const parsed = parseNotionBehaviorRow(row);
    if (!parsed?.key) continue;
    rowMap.set(parsed.key, row.id);
  }

  const intents = Object.entries(effectiveConfig.intents || {});
  for (const [intentKey, behavior] of intents) {
    const mergedBehavior = {
      ...(effectiveConfig.global || {}),
      ...(behavior || {}),
    };

    const properties = buildNotionBehaviorProperties(intentKey, mergedBehavior);
    const existingPageId = rowMap.get(intentKey);

    if (existingPageId) {
      await updateNotionBehaviorPage(token, existingPageId, properties);
    } else {
      await createNotionBehaviorPage(token, databaseId, properties);
    }
  }

  return {
    pushedAt: new Date().toISOString(),
    intentCount: intents.length,
  };
}

Enter fullscreen mode Exit fullscreen mode

Runtime behavior loading in storefront chat

This is where the storefront chat loads the published behavior config for the active shop before generating a response.

const behaviorConfig = await getChatBehaviorConfig(shopDomain || session?.shop || '');

Enter fullscreen mode Exit fullscreen mode

Then later, the response generator uses the active specialist behavior:

async function generateAssistantCopy({
  query,
  items,
  allowFollowups,
  policyAnswer,
  apiKey,
  chatHistory = [],
  behaviorConfig,
  behaviorIntent = 'search'
}) {
  // ... build shortlist and conversation context ...

  const recommendationInstruction =
    behaviorIntent === 'search_training_bags'
      ? trainingBagFollowupInstruction
      : standardFollowupInstruction;

  // behaviorConfig + behaviorIntent shape the final response
}

Enter fullscreen mode Exit fullscreen mode

Admin actions for the two sync buttons

This is the UI-facing action layer that powers Sync From Notion and Sync To Notion.

export const action = async ({ request }) => {
  const { session } = await authenticate.admin(request);
  const formData = await request.formData();
  const actionType = formData.get("action");

  if (actionType === "sync_behavior_to_notion") {
    const result = await syncChatBehaviorToNotion(session.shop);
    return json({
      ok: true,
      behaviorPush: true,
      pushedAt: result?.pushedAt || new Date().toISOString(),
      message: `Behavior pushed to Notion for ${result?.intentCount || 0} intents.`,
    });
  }

  if (actionType === "sync_behavior_from_notion") {
    const config = await syncChatBehaviorFromNotion(session.shop);
    return json({
      ok: true,
      behaviorSync: true,
      source: config?.source || "published_notion",
      syncedAt: config?.publishedAt || config?.fetchedAt || new Date().toISOString(),
      message: "Behavior synced from Notion.",
    });
  }
};

Enter fullscreen mode Exit fullscreen mode

How I Used Notion MCP

Notion MCP is what made this system possible in a meaningful way.

I used Notion MCP to create and operate a behavior database that acts like a control room for the storefront AI.

Each row in Notion represents an intent or a specialized branch, for example:

  • search
  • search_training_bags
  • search_gloves_wraps
  • cart_review
  • orders_selection
  • order_detail

Each row contains structured behavior fields like:

  • Tone Voice
  • Intent Synonyms
  • Intent Instructions
  • Questions To Ask
  • Product Recommendation Strategy
  • Do
  • Do Not
  • Example Responses
  • Behavior Notes

This let me turn Notion into an operator-facing behavior system instead of just a place to store notes.

For example:

  • the training bag specialist can focus on freestanding vs hanging bag decisions
  • the gloves and wraps specialist can focus on MMA gloves, boxing gloves, bag gloves, wraps, padding, and protection
  • the cart review specialist can behave more like a checkout assistant
  • the order specialists can stay concise and service-oriented

The important part is that Notion MCP did not just help me document the system. It helped me build the system that controls the AI behavior.

That unlocked a much stronger workflow:

  • identify a weak response
  • update the specialist behavior in Notion
  • sync it into the app
  • test it live in the storefront

That is why I think this is a strong Notion MCP use case.

I did not just connect Notion to an agent.
I used Notion MCP to build the behavior control plane for an AI commerce brain.

Cart Review Behavior:
cart review behavior

Sale Search Behavior
sale search behavior

The part I like most is that behavior is no longer buried in prompts or scattered across code. Notion became a real operating surface for the AI, and that made the storefront feel less like a chatbot and more like a system of specialists that can actually be tuned, tested, and improved over time.

Top comments (0)