This is a submission for the Notion MCP Challenge
What I Built
Formlink — AI-powered conversational intake forms that write directly to Notion.
No static fields. Respondents have a back-and-forth chat with Claude. When enough info is collected, Claude calls Notion MCP and creates a structured row. No manual API wrapper. Claude decides when it has enough data.
Two flows:
- Creator — connect Notion, pick a DB, chat with Claude to configure the form (field contexts, slug). Publish. Share link.
-
Respondent — visit
/f/slug, have a conversation, data lands in Notion.
Stack: TanStack Start · Anthropic Claude · Notion MCP (self-hosted on Railway) · Appwrite
Video Demo
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
Repo: https://github.com/immanewel/formlink
Live: https://formlink.appwrite.network
How I Used Notion MCP
Claude connects to a self-hosted Notion MCP server at runtime via Anthropic's mcp_servers API parameter:
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
system: buildSystemPrompt(form, schema),
messages: history,
mcp_servers: [{
type: 'url',
url: process.env.NOTION_MCP_URL,
name: 'notion',
authorization_token: notionToken
}],
tools: [{ type: 'mcp_toolset', mcp_server_name: 'notion' }]
}, { headers: { 'anthropic-beta': 'mcp-client-2025-11-20' } })
Claude discovers notion_create_page dynamically — no manual tool schema. The system prompt tells it which database and what fields to collect. Claude handles the rest, including deciding when to write.
What this unlocks: zero Notion API glue code. The AI decides when the form is complete and writes it. The app just checks for mcp_tool_result in the response.
Three things that tripped me up:
- Notion OAuth returns a bot user — the human identity is in
tokenResponse.owner.user - MCP response blocks are
mcp_tool_use/mcp_tool_result, not the regulartool_usetypes - System prompt must explicitly pass
parent: { database_id }or Notion returns 400
One Cost Insight Worth Sharing
Exposing all Notion MCP tools loads every unused tool schema on every API call. Restricting to allowed_tools: ['notion_create_page'] eliminates that
overhead entirely — Formlink only ever creates pages, so there's no reason to pay for the rest.
Top comments (0)