This is a submission for the Notion MCP Challenge
What I Built
Formlink — AI-powered intake system. Replaces static forms with natural conversations, writes structured data directly into Notion via MCP.
Instead of a 20-field form 60% of people abandon, Formlink has a 3-minute conversation — asking smart follow-ups, skipping irrelevant fields, writing clean structured data into Notion the moment the conversation ends.
Forms should listen, not interrogate.
What makes it different
1. AI branching — no conditional rules
A founder saying "just me, early stage" gets 3 questions. An enterprise team gets 6. Same form. Different conversations. No IF/THEN rules.
2. Creator field context — the real differentiator
Creators add intent per field:
"Budget Range — context: below $5K is self-serve tier, above gets personal follow-up"
AI reads this, asks smarter questions. Instead of "What's your budget?" it asks "What budget have you set aside — and is there flexibility if scope grows?" Nobody else does this.
3. MCP at runtime — not just during development
Claude connects to Notion MCP server during every form submission — discovering tools, calling notion_create_page, writing structured data to the creator's workspace. Not a REST API call dressed as MCP. Actual MCP tool execution at runtime.
Video Demo
No video — here's a written walkthrough instead.
1. Creator sets up a form
Connect Notion → dashboard shows shared DBs → "Create Form" → two-panel screen.
Claude asks ~5 questions: purpose, field contexts. Emits form config. Pick slug, hit Publish.
2. Respondent fills it out
Visit formlink.appwrite.network/f/enterprise-software-dev-intake — no fields, no dropdowns.
AI asks one question at a time, follows up on vague answers, skips irrelevant fields.
When done, calls Notion MCP. Row created.
3. Data lands in Notion
Open Notion — row is there. Every property filled. No sync, no Zapier, no webhook.
Submission logged in dashboard with direct link to Notion page.
Show Us the Code
Live demo: formlink.appwrite.network
GitHub: github.com/immanewel/formlink
How I Used Notion MCP
MCP is central to Formlink in two ways — at runtime and during development.
At Runtime — The Anthropic MCP Connector
Every form submission:
Respondent types answer
↓
TanStack Start server function
↓
Anthropic Messages API (with mcp_servers parameter)
↓
Claude connects to Notion MCP server on Railway
↓
Claude calls notion_create_page via MCP
↓
Structured row appears in creator's Notion database
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 1024,
system: buildSystemPrompt(form, schema),
messages: conversationHistory,
mcp_servers: [
{
type: 'url',
url: process.env.NOTION_MCP_URL,
name: 'notion',
authorization_token: userNotionToken
}
],
tools: [
{
type: 'mcp_toolset',
mcp_server_name: 'notion',
allowed_tools: ['notion_create_page'] // 20x cheaper than exposing all tools
}
]
}, {
headers: { 'anthropic-beta': 'mcp-client-2025-11-20' }
})
const mcpResult = response.content.find(b => b.type === 'mcp_tool_result')
if (mcpResult) {
await saveSubmission(form.$id, mcpResult)
return { type: 'complete' }
}
During Development — Claude Code + Notion MCP
Claude Code uses Notion MCP via stdio throughout the build. Before writing any integration code, used MCP to explore schema directly:
"Show me the properties of my Formlink — Consulting Intake database"
"Create a test page with these field values"
"What select options does Budget Range accept?"
Every piece of code already worked before writing it — property names, types, values all verified via MCP first.
Same MCP server, two modes:
-
Dev:
claude code→ Notion MCP via stdio → explore, test, verify - Runtime: Anthropic API → Notion MCP via HTTP → create pages during submissions
Architecture
┌─────────────────────────────────────────────────┐
│ Formlink │
│ │
│ Creator Dashboard (Appwrite auth) │
│ → Connects Notion via OAuth │
│ → Picks database → Conversational form setup │
│ → Gets shareable link: /f/[slug] │
│ │
│ Public Intake Route (/f/[slug]) │
│ → Respondent chats (no account needed) │
│ → Server function calls Claude API │
│ → Claude connects to Notion MCP server │
│ → notion_create_page called via MCP │
│ → Row lands in creator's Notion database │
└─────────────────────────────────────────────────┘
Infrastructure:
App: Appwrite Sites (TanStack Start, SSR)
Auth + DB: Appwrite (auth, forms, submissions)
Notion MCP: @notionhq/notion-mcp-server on Railway (HTTP/SSE)
AI: Anthropic Claude Sonnet via MCP connector
One Cost Insight Worth Sharing
Exposing all Notion MCP tools loads every tool schema on every API call (~8,000 tokens overhead). Restricting to allowed_tools: ['notion_create_page'] drops this to ~400 tokens — 20x cost reduction. Cost per submission goes from ~$1.00 to ~$0.05.
The Stack
| Layer | Choice |
|---|---|
| Framework | TanStack Start |
| Auth + DB | Appwrite |
| AI | Anthropic Claude Sonnet |
| Notion | Notion MCP (runtime + dev) |
| MCP Host | Railway |
| Hosting | Appwrite Sites |
What's Next
Phase 1 — done:
- Notion OAuth per user — any creator connects their own workspace
- Dynamic schema — any Notion database, not just the demo
- Form expiration + custom slugs
- Submission tracking dashboard
Top comments (0)