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_bagssearch_gloves_wrapscart_revieworders_selectionorder_detail
This makes the behavior visible, editable, and operational.
I also built a publish model so Notion is safe to use in production:
- Edit behavior in Notion
- Click
Sync from Notion - Publish the behavior into the app
- 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.
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']),
};
}
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;
}
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,
};
}
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 || '');
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
}
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.",
});
}
};
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:
searchsearch_training_bagssearch_gloves_wrapscart_revieworders_selectionorder_detail
Each row contains structured behavior fields like:
Tone VoiceIntent SynonymsIntent InstructionsQuestions To AskProduct Recommendation StrategyDoDo NotExample ResponsesBehavior 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.
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)