Part of One Last Commit: small ideas, taken further than strictly necessary.
This package has moved. It was previously published as
@atomiclabs97/onenote-mcpand then@1percentlabs/onenote-mcp, and is now@onelastcommit/onenote-mcp. If you have an old name in a config file, switch to the new one. The old packages are deprecated and no longer updated. Versions carry on unbroken, so0.2.1is the direct successor to0.2.0.
An MCP server for Microsoft OneNote. Bring your notebooks into Claude, Cursor, and any MCP-compatible client — list notebooks and sections, full-text search across pages, read individual pages, and create or delete pages from natural language. Authentication uses Microsoft's device-code flow against your own Entra ID app registration, so your data and credentials never leave your machine.
Screenshot/demo coming soon — drop a GIF in
docs/images/demo.gifand reference it here.
| You want to use OneNote in… | Run mode | Start here |
|---|---|---|
| Claude Desktop, Cursor, Claude Code on your laptop | stdio — the server runs as a subprocess of the client; tokens never leave your disk |
Quick start below |
| claude.ai web + iOS/Android, or any remote client | http — you self-host the server and add it as a Connector |
Remote transport → Self-hosting |
Stdio is the default and needs no hosting. The HTTP transport is opt-in.
The server talks to Microsoft Graph using a Microsoft Entra (Azure AD) app registration that you own. This takes about 2 minutes.
Screenshots referenced below live in
docs/images/. They are placeholders today — contributions welcome.
-
Go to the Microsoft Entra admin center → App registrations and click + New registration.
-
Name: anything (e.g.
onenote-mcp). -
Supported account types: choose "Accounts in any organizational directory and personal Microsoft accounts" if you want both work and personal OneNote to work; otherwise pick what matches your tenant.
-
Redirect URI: leave blank — the device-code flow doesn't need one.
-
Click Register.
-
In the new app's left nav, click API permissions → + Add a permission → Microsoft Graph → Delegated permissions.
-
Search for and check both:
Notes.ReadWriteoffline_access
-
Click Add permissions. (No admin consent is required for personal Microsoft accounts. Work/school tenants may need an admin to grant consent for the directory.)
The device-code flow is a "public client" flow — it doesn't use a client secret.
-
Go to Authentication in the left nav.
-
Scroll to Advanced settings → Allow public client flows and toggle it Yes.
-
Click Save.
Back on the app's Overview page, copy the Application (client) ID. You'll pass it to the server via the ONENOTE_MCP_CLIENT_ID environment variable.
ONENOTE_MCP_CLIENT_ID=<your-app-client-id> npx @onelastcommit/onenote-mcp loginThis prints a code and a URL like:
To sign in, use a web browser to open https://microsoft.com/devicelogin
and enter the code ABCD-1234 to authenticate.
Open the URL, paste the code, sign in with the Microsoft account whose OneNote you want to access, and approve the requested permissions. The refresh token is then cached at ~/.config/onenote-mcp/tokens.json (mode 600) so the MCP server can run silently.
To sign out:
npx @onelastcommit/onenote-mcp logoutEdit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
Restart Claude Desktop. The OneNote tools should appear in the tool picker.
Add to ~/.cursor/mcp.json:
{
"mcpServers": {
"onenote": {
"command": "npx",
"args": ["-y", "@onelastcommit/onenote-mcp"],
"env": {
"ONENOTE_MCP_CLIENT_ID": "your-app-client-id"
}
}
}
}Any MCP-compatible client that supports stdio servers will work. Run npx @onelastcommit/onenote-mcp with ONENOTE_MCP_CLIENT_ID set in the environment.
In addition to the default stdio mode, the server can speak the MCP Streamable HTTP transport. Use this when the MCP client lives somewhere other than the same machine as the server — for example, claude.ai Connectors (web, iOS, Android), a self-hosted bridge on a home server, or a backend integration.
Stdio is still the default. Nothing changes for existing Claude Desktop / Cursor users.
| Transport | When |
|---|---|
stdio (default) |
Local desktop clients (Claude Desktop, Cursor, Claude Code). Tokens never leave your disk; the server runs as a subprocess of the client. |
http |
Remote clients (claude.ai web/mobile Connectors), self-hosted deployments, any setup where the server runs separately from the client. |
# Generate a strong shared secret first — clients must send this as `Authorization: Bearer <token>`.
export ONENOTE_MCP_HTTP_TOKEN="$(openssl rand -base64 32)"
ONENOTE_MCP_CLIENT_ID=<your-app-client-id> \
npx @onelastcommit/onenote-mcp --transport http --port 3000The server refuses to start without ONENOTE_MCP_HTTP_TOKEN — a remote MCP endpoint with no auth is the entire world reading your OneNote.
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST / GET / DELETE |
/mcp |
Authorization: Bearer <token> |
MCP Streamable HTTP transport. All tools mounted here. |
GET |
/healthz |
none | Liveness probe; returns 200 with no body. For platform health checks. |
Bearer-token comparison is constant-time. Wrong or missing tokens return 401 Unauthorized with a WWW-Authenticate: Bearer header.
| Flag | Env var | Default | Notes |
|---|---|---|---|
--transport <stdio|http> |
— | stdio |
|
--host <addr> |
ONENOTE_MCP_HTTP_HOST |
127.0.0.1 |
Set to 0.0.0.0 to listen on all interfaces (containers / PaaS). |
--port <n> |
ONENOTE_MCP_HTTP_PORT |
3000 |
Flags win over env vars.
# Sanity check
curl http://127.0.0.1:3000/healthz # → 200
curl -X POST -H "Authorization: Bearer $ONENOTE_MCP_HTTP_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \
http://127.0.0.1:3000/mcpSign in with npx @onelastcommit/onenote-mcp login (one-time, on the host running the server) before any tools are invoked — the device-code flow is the same as the stdio path and caches tokens at ~/.config/onenote-mcp/tokens.json. The bearer token is server↔client auth only; the Microsoft Graph credentials still come from the cached refresh token.
On a headless host (a container, a PaaS) there's no terminal for the device-code login. Run login on your laptop instead, then pass the resulting tokens.json to the server via the ONENOTE_MCP_TOKEN_CACHE env var — on first boot in HTTP mode the server seeds its cache from it. The deployment guides walk through this.
- Pick a new token:
openssl rand -base64 32. - Update
ONENOTE_MCP_HTTP_TOKENand restart the server. - Update the token everywhere a client uses it.
No session state is kept between requests, so rotation is just a restart — there is nothing to migrate.
No. v0.2 assumes one OneNote account per deployment. The bearer token gates access to whichever account onenote-mcp login was last run for on the host. If you need per-user separation today, run one instance per user.
- The HTTP transport is stateless: each request gets a fresh transport, no
Mcp-Session-Idis issued, and the server does not push notifications. Tools that perform a single request → single response (i.e. all eleven shipped tools) work normally. - No rate-limiting beyond the bearer-token gate. Put the server behind a reverse proxy / API gateway if you need it.
To use OneNote from claude.ai (web + mobile) you host the HTTP transport somewhere with a public HTTPS URL, then add it as a custom Connector. Copy-paste recipes:
| Guide | For |
|---|---|
| Fly.io | Fastest path — free-tier eligible, ~10 minutes, HTTPS handled for you. |
| Docker (VPS / home server) | Any host you control; uses the published ghcr.io/onelastcommit/onenote-mcp image. |
| claude.ai Connector | Wiring the deployed URL into claude.ai and verifying the tools. |
Released Docker images are pushed to the GitHub Container Registry on every version tag.
| Tool | Description | Key inputs |
|---|---|---|
list_notebooks |
Lists all OneNote notebooks for the signed-in user. | (none) |
list_sections |
Lists sections, optionally scoped to a single notebook. | notebookId? |
list_section_groups |
Lists section groups (folders), optionally scoped to a notebook. | notebookId? |
search_pages |
Full-text search across pages (title + content). | query, limit? |
read_page |
Returns page metadata + content (HTML or Markdown). | pageId, format? (html | markdown) |
create_notebook |
Creates a new top-level notebook. | name |
create_section |
Creates a section inside a notebook or section group. | notebookId? | sectionGroupId?, name |
create_section_group |
Creates a section group inside a notebook or another section group. | notebookId? | sectionGroupId?, name |
create_page |
Creates a page in a section. Accepts Markdown (default) or HTML, plus optional binary attachments. | sectionId, title, content, format?, attachments? |
update_page |
Applies edits to a page: append/prepend/insert/replace/delete elements. | pageId, operations[] |
delete_page |
Permanently deletes a page. Irreversible. | pageId |
| Env var | Required | Description |
|---|---|---|
ONENOTE_MCP_CLIENT_ID |
yes | Application (client) ID of your Microsoft Entra app registration. |
ONENOTE_MCP_TENANT_ID |
no | Tenant ID. Defaults to common, which works for both personal and work accounts. |
ONENOTE_MCP_HTTP_TOKEN |
http only | Shared bearer token for the HTTP transport. Required when --transport http. |
ONENOTE_MCP_HTTP_HOST |
no | Bind address for the HTTP transport. Defaults to 127.0.0.1. --host wins. |
ONENOTE_MCP_HTTP_PORT |
no | Listen port for the HTTP transport. Defaults to 3000. --port wins. |
ONENOTE_MCP_TOKEN_CACHE |
no | Verbatim tokens.json contents used to seed the cache on a headless host. Applied only in --transport http mode when no cached token exists yet. |
XDG_CONFIG_HOME |
no | Override the config directory. Tokens are stored at <dir>/onenote-mcp/tokens.json. |
- Attachments are sent in-memory.
create_pagereads attachment files fully into memory before posting; very large files (~150 MB+) may strain Node's heap. Streamed uploads are a future enhancement. update_pagetargets are rawdata-idselectors. To edit a specific element, read the page first and pull thedata-idattribute out of the returned HTML. Higher-level selectors (e.g. "the section under heading X") are tracked for a follow-up.- Search latency. Microsoft Graph's
$searchagainst/me/onenote/pagescan take a few seconds against large notebooks; the server retries on 429s with exponential backoff.
PRs welcome. The codebase aims to stay small and focused.
git clone https://github.com/onelastcommit/onenote-mcp
cd onenote-mcp
yarn install
yarn build
yarn testyarn typecheck—tsc --noEmit(coverssrc/+scripts/)yarn lint— ESLintyarn test— Vitest (unit tests, mocked Graph)yarn dev— incremental rebuild on save (tsc --watch+tsc-alias --watch);dist/stays runnable
Use Yarn (Classic), not npm. The repo's lockfile is
yarn.lock—package-lock.jsonshould not be committed.
scripts/smoke.ts exercises every shipped tool against a real OneNote account. It's not in CI — needs real credentials.
# One-time: register an Entra app (see Quick start above) and sign in
ONENOTE_MCP_CLIENT_ID=<your-client-id> yarn build
ONENOTE_MCP_CLIENT_ID=<your-client-id> node dist/cli.js login
# Then run the smoke test
ONENOTE_MCP_CLIENT_ID=<your-client-id> yarn smokeIt's idempotent: reuses (or creates) a notebook called OneNote MCP Smoke Test, walks through 13 steps covering auth, list/search/read/create/update/delete and the multipart attachment path, and cleans up the pages it creates. Sections and section groups are left behind for the next run.
If you're using an AI coding assistant (Claude Code, Cursor, etc.), see CLAUDE.md for project conventions, the arrow-function rule, and the tool-authoring checklist.
Commits follow Conventional Commits (feat:, fix:, chore:, etc.). CI runs typecheck + lint + test + build on every push and PR.
-
update_page— in-place edits via Graph'sPATCHsyntax -
create_section/create_notebook - Image and attachment upload (multipart create_page)
- Section group support (list + create + section-in-group targeting)
- HTTP (Streamable HTTP) transport with bearer-token auth
- Self-hosted deployment guide (Dockerfile + Fly.io recipe + claude.ai Connector validation)
- Streamed attachment uploads (avoid in-memory buffering for large files)
- Resource-style page browsing (alongside the tool surface)
MIT © Ahmad Al Mezaal




{ "mcpServers": { "onenote": { "command": "npx", "args": ["-y", "@onelastcommit/onenote-mcp"], "env": { "ONENOTE_MCP_CLIENT_ID": "your-app-client-id" } } } }