notion-mcp-hono
Remote MCP server that exposes Notion as tools (search, get, create pages, append blocks, query databases) via Streamable HTTP with bearer auth, designed for deployment on Railway.
README
notion-mcp-hono
A remote MCP server exposing Notion as tools — TypeScript, Hono, the official MCP TypeScript SDK over Streamable HTTP, deployed on Railway.
Notion ships an official MCP server; this project is not trying to replace it. It is a portfolio piece demonstrating how to build a remote MCP server properly: focused scope, test-driven, authenticated, observable, and deployed.
Tools
| Tool | What it does |
|---|---|
search_pages |
Search pages across the workspace by title |
get_page |
Retrieve one page as clean markdown |
create_page |
Create a page under a parent page or database |
append_blocks |
Append markdown to the end of an existing page |
query_database |
Query a database with Notion filters and sorts, rows simplified to flat JSON |
All tool inputs are validated with Zod (with size limits), all descriptions are written for the LLM consuming them, and all Notion failures come back as actionable, in-band error messages — never raw API errors.
Architecture
┌──────────────────────────────────────────────┐
MCP client │ notion-mcp-hono │
(claude.ai / │ │
Claude Code) │ Transport layer MCP layer │
│ │ ┌───────────────┐ ┌───────────────┐ │
│ POST /mcp │ │ Hono app │ │ McpServer │ │ Notion API
├─────────────────►│ │ bearer auth ├──────►│ 5 tools │ │
│ Authorization: │ │ body limit │ │ zod schemas │ │
│ Bearer <key> │ │ req logging │ │ tool runner │ │
│ │ └───────────────┘ └───────┬───────┘ │
│ GET /health │ │ │ │
├─────────────────►│ 200, no auth ┌────────▼────────┐ │
│ │ │ Notion layer │ │ ┌─────────┐
│ │ │ NotionGateway ├──┼──►│ Notion │
│ │ │ (interface) │ │ └─────────┘
│ │ │ SDK v5 adapter │ │
│ │ └─────────────────┘ │
└──────────────────┴──────────────────────────────────────────────┘
Three layers, dependency arrows pointing inward:
- Transport (
src/app.ts,src/auth.ts) — Hono routes, bearer auth, body limit, request logging. - MCP (
src/mcp/) — server setup, the 5 tool registrations, the tool runner (error handling + per-call logging). - Notion (
src/notion/) — theNotionGatewayinterface (domain types only) and its@notionhq/clientadapter. The MCP layer never imports the Notion SDK, so the integration is swappable without touching tool code.
Quickstart (local)
git clone <this repo> && cd notion-mcp-hono
npm install
cp .env.example .env # then fill in the two variables below
npm run dev # http://localhost:3000
You need two secrets in .env (or exported):
| Variable | What it is |
|---|---|
NOTION_TOKEN |
An internal integration token from notion.so/my-integrations. Share the pages/databases you want accessible with that integration (page → ⋯ → Connections). |
MCP_API_KEY |
The bearer key clients of this server must present. Generate one: openssl rand -hex 32. |
Check it's alive:
curl http://localhost:3000/health
# {"status":"ok"}
curl -s -X POST http://localhost:3000/mcp \
-H "Authorization: Bearer $MCP_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Authentication
Every request to /mcp requires:
Authorization: Bearer <MCP_API_KEY>
- Comparison is timing-safe (both sides are SHA-256-hashed, then
crypto.timingSafeEqual— neither content nor length leaks through response timing). - Missing/wrong token →
401with a JSON body naming the expected header. GET /healthis public (it's the deployment health check).
Connecting from Claude
claude.ai custom connector
Settings → Connectors → Add custom connector:
- URL:
https://<your-deployment>.up.railway.app/mcp - Advanced settings → HTTP headers: add
Authorization=Bearer <MCP_API_KEY>
Claude Code
claude mcp add --transport http notion-hono \
https://<your-deployment>.up.railway.app/mcp \
--header "Authorization: Bearer <MCP_API_KEY>"
Then /mcp inside Claude Code shows the connection; the five tools appear as
mcp__notion-hono__*.
Deploy on Railway
-
Create a project from this repo (Railway dashboard → New Project → Deploy from GitHub repo). The default Nixpacks build works as-is:
npm ci && npm run build && npm start. -
Set the environment variables on the service:
NOTION_TOKEN— your Notion internal integration tokenMCP_API_KEY— the bearer key for clients (generate:openssl rand -hex 32)
PORTis injected by Railway automatically; the server reads it. -
Set the service health check path to
/health. -
Generate a public domain (service → Settings → Networking) and use
https://<domain>/mcpas the connector URL.
The server fails fast at boot with a clear message if either env var is missing.
Smoke test (manual, not CI)
Exercises a running server end-to-end against a real Notion workspace:
MCP_URL=https://<your-deployment>.up.railway.app \
MCP_API_KEY=<key> \
SMOKE_PARENT_PAGE_ID=<page id> # optional: enables create_page + append_blocks \
SMOKE_DATABASE_ID=<database id> # optional: enables query_database \
npm run smoke
It verifies /health, the 401 on unauthenticated /mcp, the initialize handshake,
tools/list, and each tool (write tools only when their env var is set — pages it
creates are titled Smoke test <timestamp> and safe to delete).
Development
npm test # Vitest, all Notion calls mocked — no network
npm run test:watch
npm run typecheck # strict, includes tests and scripts
npm run build # compiles src/ to dist/
Tests run at three altitudes: pure functions (property simplification, auth, logger),
the MCP layer via InMemoryTransport with a fake gateway, and full HTTP integration
using the real MCP SDK client against the Hono app on an ephemeral port.
Design decisions
- Streamable HTTP, stateless mode. This is a remote server, so stdio is out. Of Streamable HTTP's two modes we run stateless: a fresh
McpServer+ transport per request, noMcp-Session-Id, no session map. Every tool is pure request/response, so sessions would only buy horizontal-scaling problems. ConsequentlyGET/DELETE /mcpreturn405(nothing to stream or terminate). - Markdown conversion is delegated to Notion. Notion's 2025-09-03 API has native markdown endpoints (
pages.retrieveMarkdown,pages.updateMarkdown). Hand-rolling a blocks↔markdown converter is an entire class of fidelity bugs this project chose not to own. NotionGatewayinterface as the seam. MCP tools depend on a 5-method interface speaking domain types (markdown strings, flat property values). The@notionhq/clientadapter — including thedatabase_id→ data-source resolution the 2025 API requires — is invisible to tool code and replaceable without touching it.- Errors are a UX surface for the model. Zod validation failures and Notion errors return in-band (
isError: true) with messages that say what to fix ("share the page with the integration via ⋯ → Connections"), so the calling LLM can self-correct. Unexpected errors return a generic in-band message and are logged server-side — raw internals never reach the client. - Auth is boring on purpose. One static bearer key, timing-safe compare, injected via
createApp({ mcpApiKey })so tests never touchprocess.env. OAuth is the spec's long-term answer for multi-user servers; for a single-workspace personal server it's complexity without benefit. - Structured logs to stdout. One JSON line per HTTP request and per tool call (name, outcome, duration — never arguments, which can contain page content). Railway captures stdout;
/healthis excluded so the poller doesn't drown the signal.
License
MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。