Notion 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 de
Описание
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
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.
Live demo
notion-mcp-hono-production.up.railway.app —
the root URL of the MCP endpoint doubles as a landing page with a live playground:
pick a read-only tool (search_pages, get_page, query_database), fill a form
generated from the tool's actual zod schema, and run it against a dedicated demo
workspace. Next to each result you see the JSON-RPC payload an MCP client would send.
The playground's /demo/* routes hold credentials server-side, expose the read-only
tools only (the write tools have no route entry — no input reaches them), and are
rate-limited per IP (10 requests/minute).
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)DEMO_NOTION_TOKEN(optional) — a second integration token for a dedicated demo workspace, powering the public playground at/demo/*. Leave it unset to disable the playground (its routes then respond503); never point it at a workspace with real data.
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
Установка Notion Hono
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/ldamoredev/notion-mcp-honoFAQ
Notion Hono MCP бесплатный?
Да, Notion Hono MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Notion Hono?
Нет, Notion Hono работает без API-ключей и переменных окружения.
Notion Hono — hosted или self-hosted?
Доступен hosted-вариант: Unyly запускает сервер в облаке, локальная установка не обязательна.
Как установить Notion Hono в Claude Desktop, Claude Code или Cursor?
Открой Notion Hono на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
Notion
Read and write pages in your workspace
автор: NotionLinear
Issues, cycles, triage — from Claude
автор: LinearGoogle Drive
Search and read your Drive files
автор: Googlemindsdb/mindsdb
Connect and unify data across various platforms and databases with [MindsDB as a single MCP server](https://docs.mindsdb.com/mcp/overview).
автор: mindsdbCompare Notion Hono with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории productivity
