OneNote MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@OneNote MCP Serverlist my notebooks"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
OneNote MCP Server
A local MCP (Model Context Protocol) server that lets Claude Desktop, or any other MCP client, read and edit pages in the OneNote desktop app on Windows through OneNote's COM automation API.
No Azure app registration, no Microsoft Graph, no cloud authentication: the server talks directly to the OneNote application running on your machine.
Foreword
I'm a software developer, but not a Python developer, so I built this MCP server relying heavily on an AI coding agent. I needed a reliable MCP server to create and edit OneNote pages and couldn't find an existing solution that did this well, so I built my own. Other solutions seemed to focus on pulling page content out for search and summarization with an LLM.
I tried to keep token usage as low as possible and built in basic safeguards by restricting notebook access. I still recommend regular backups before you let an LLM access your notebooks.
I hope this helps someone in a similar situation. Feel free to use the contents of this repo in any way you like.
Related MCP server: OneNote MCP Server
Requirements
Windows 10/11
OneNote desktop (2016 or Microsoft 365). The Microsoft Store "OneNote for Windows 10" app has no COM interface and does not work.
Python 3.10+, unless you use the standalone executable
Installation
There are three ways to install the server:
Standalone executable: a single
onenote-mcp.exe, no Python needed.Install with pip: installs the
onenote-mcpcommand into a Python venv.From a clone: for development.
Then check the setup.
Standalone executable
Every GitHub release ships a
self-contained onenote-mcp.exe that needs no Python installation. The asset names
stay the same across releases; the version is the release tag.
Executable: https://github.com/noelroehrig/onenote-mcp/releases/latest/download/onenote-mcp.exe
Checksum: https://github.com/noelroehrig/onenote-mcp/releases/latest/download/onenote-mcp.exe.sha256
The exe is not code-signed. Windows SmartScreen may warn about an unrecognized app, and some antivirus scanners flag self-extracting PyInstaller executables as a false positive. Verify the checksum before you allow it; Standalone executable details explains the trade-offs.
Verify the checksum. In PowerShell, in the download folder (prints True when
the file is intact):
(Get-FileHash onenote-mcp.exe -Algorithm SHA256).Hash -eq (Get-Content onenote-mcp.exe.sha256).Split(' ')[0]The .sha256 file uses the sha256sum format, so sha256sum -c onenote-mcp.exe.sha256
works as well (for example in Git Bash).
Configure Claude Desktop. Move the exe to a permanent location, for example
%LOCALAPPDATA%\onenote-mcp\, and point %APPDATA%\Claude\claude_desktop_config.json
at it:
{
"mcpServers": {
"onenote": {
"command": "C:\\Users\\<name>\\AppData\\Local\\onenote-mcp\\onenote-mcp.exe",
"env": { "ONENOTE_ALLOWED_NOTEBOOKS": "SharedNotebook" }
}
}
}ONENOTE_ALLOWED_NOTEBOOKS limits the server to the listed notebooks; see
Configuration for all options.
Install with pip
Install straight from GitHub into a fresh venv:
py -m venv %USERPROFILE%\onenote-mcp
%USERPROFILE%\onenote-mcp\Scripts\python.exe -m pip install git+https://github.com/noelroehrig/onenote-mcpThese are cmd commands; in PowerShell write $env:USERPROFILE instead of %USERPROFILE%.
Then point Claude Desktop at the installed onenote-mcp.exe console script. On a
machine with notebooks you care about, restrict the server to a dedicated notebook
right away:
{
"mcpServers": {
"onenote": {
"command": "C:\\Users\\<name>\\onenote-mcp\\Scripts\\onenote-mcp.exe",
"env": { "ONENOTE_ALLOWED_NOTEBOOKS": "SharedNotebook" }
}
}
}From a clone
git clone https://github.com/noelroehrig/onenote-mcp
cd onenote-mcp
py -m venv .venv
.venv\Scripts\python.exe -m pip install -e .Add this to %APPDATA%\Claude\claude_desktop_config.json, with the absolute path to
your clone. Claude Desktop does not start servers from the repo directory, so relative
paths do not work.
{
"mcpServers": {
"onenote": {
"command": "C:\\Projects\\onenote-mcp\\.venv\\Scripts\\python.exe",
"args": ["-m", "onenote_mcp.server"]
}
}
}Alternatively, set command to the absolute path of the console script
.venv\Scripts\onenote-mcp.exe inside the clone and leave out args.
Other MCP clients
Any MCP client that starts stdio servers works the same way: use the command,
args and env values from the examples above.
Check the setup
OneNote desktop must be running (or startable) with at least one notebook open.
Restart Claude Desktop; the OneNote tools should appear in the tools list. Then ask
Claude to call the ping tool: {"server": "ok", "onenote_responsive": true, "config_error": null}
means the server, the COM bridge and OneNote are all talking to each other. A
non-null config_error names a configuration problem (such as an unexpanded
${...} placeholder in ONENOTE_ALLOWED_NOTEBOOKS) that blocks the notebook
tools even though OneNote itself is reachable.
Tools
Navigation
Tool | What it does |
| Open notebooks with their sections (no pages): the small navigation skeleton. |
| The pages of one section in section order, as |
The intended flow: get_notebooks → pick a section → list_pages(section_id) →
pick a page → read/write tools below.
Reading
Tool | What it does |
| A page as structured JSON: paragraphs, headings, lists, indented content, inline and floating images, plus read-only markers for content the structured tools cannot write. Image bytes are not included; images carry compact |
| The base64 bytes for a single image handle, fetched on demand. |
| Check whether image handles are still writable, without writing anything. Fetches and caches the bytes of handles not cached yet, so |
Writing
Tool | What it does |
| Create a page in a section, optionally as a sub-page of an existing page. Returns the new page id. |
| Replace a page's entire content from structured JSON (outlines + floating images). Rejects read-only |
| Append one structured content block to an existing page, preserving current content. Rejects read-only |
Health
Tool | What it does |
| Fast health check: is the server alive, is OneNote responding, and is a configuration error blocking the notebook tools? Works even while another call is stuck on a wedged OneNote. |
Raw-XML escape hatches
Prefer the structured tools above; these exist for direct schema control and debugging.
They cost significantly more tokens because of the XML overhead. Set
ONENOTE_DISABLE_RAW_XML to 1 to remove them from the server.
Tool | What it does |
| The entire notebook → section → page tree as raw OneNote XML (can be very large). |
| The full |
| Replace a page from a caller-supplied |
| Append a single raw XML element (e.g. |
Image handles (mcpref:)
Reading a page never ships image bytes to the client. Each image is represented
by a short opaque handle such as mcpref:a1b2c3d4e5f6, together with its size and
position. This keeps reads of image-heavy pages small and fast.
To preserve images when rewriting a page, copy the handles (or the whole
imageslist fromget_page) verbatim intoreplace_page/append_page. The server resolves handles back to real bytes just before writing to OneNote.To inspect an image's pixels, call
get_image_datawith its handle.Handles live for the lifetime of the server process. If a handle has gone stale (e.g. after a restart), re-read the source page with
get_pageto mint fresh ones.
Content the structured tools cannot write
Tables, handwriting and drawings (ink), attached files, audio/video recordings and
content from newer OneNote versions have no structured item type. get_page still
reports them, so nothing disappears unnoticed:
Inside an outline, each becomes a read-only item such as
{"type": "unsupported", "kind": "table", "text": "Name | Age\nAda | 36"}.kindistable,ink,file,mediaorunknown;textis the readable text when OneNote provides it (table cells one row per line, recognized handwriting, a file's name).Objects placed directly on the page canvas, such as handwriting, are listed in the top-level
unsupportedlist with their kind, position and size.
replace_page and append_page reject a payload that still contains an
unsupported item with bad_request, because rewriting the page would delete that
content. Remove the item deliberately only if deleting it is intended. The page-level
unsupported objects cannot be passed back at all, so replace_page keeps them in
place at their position: place new content where it does not overlap them.
replace_page_xml still replaces everything, since its caller supplies the full page.
Error codes
Tool errors start with a machine-readable code so clients can react without parsing prose:
Code | Meaning |
| OneNote did not respond within the per-operation deadline, most likely because it is showing a modal dialog or syncing. Dismiss any dialog and retry; |
| OneNote returned a COM failure (bad id, locked content, …). |
| The request itself was invalid (unknown image handle, malformed content, …). |
| The server's configuration is invalid, e.g. |
| A replace wrote the new content, but some old page objects could not be deleted and are still on the page. The message lists each one's kind, object id and hresult. Read the page with |
Configuration
All configuration is via environment variables (set them in the env block of
the Claude Desktop server entry if needed):
Variable | Default | Meaning |
| (unset: no restriction) | Comma-separated notebook names, e.g. |
|
| Timeout (s) for page/hierarchy reads and image fetches. |
|
| Timeout (s) for page creates and writes. |
|
| Timeout (s) for the |
|
| In-memory cap for cached image bytes; least-recently-used entries are evicted beyond it. |
| (unset: raw-XML tools on) |
|
Standalone executable details
Why a single file, and what it costs
The exe is a PyInstaller --onefile build because a single file is the easiest thing
to download, verify and reference from a config. A --onedir build starts faster and
usually trips antivirus less often, but it is a folder with the whole Python runtime
that has to be unzipped and kept together. The single file costs:
Startup time. Every launch unpacks the bundled runtime into a temporary
%TEMP%\_MEI*folder, which is removed on exit. The server answers after about 1.5 s instead of about 0.7 s from a venv, and the first launch after a download can take longer while antivirus scans the file. Claude Desktop starts the server once per session, so this is paid once.Antivirus false positives. Self-extracting PyInstaller executables are a common antivirus false positive, and this exe is not code-signed. If your scanner quarantines it, check the SHA256 before allowing it, and consider reporting the false positive to the vendor. Windows SmartScreen may also warn about an unrecognized app when you start the exe from Explorer.
COM inside the exe
comtypes generates Python wrappers for OneNote's COM type library the first time the
server connects to OneNote. The packages bundled in the exe are read-only, so comtypes
writes the wrappers to %TEMP%\comtypes_cache\onenote-mcp-314 instead (the suffix is
the bundled Python version) and reuses them on later launches. They cannot be
generated at build time: the type library ships inside ONENOTE.EXE, which the build
machine does not have. In the exe, comtypes does not check whether the type library
changed, so if OneNote calls start failing after an Office update, delete that folder
and the wrappers are regenerated on the next start.
Development
Source layout:
src/onenote_mcp/
server.py MCP tool definitions (FastMCP, stdio transport)
com.py COM wrapper: threading, timeouts, image-handle cache, notebook allowlist
builders.py model <-> OneNote XML conversion
models.py Pydantic models = the JSON schema of the structured tools
images.py pixel size from image header bytes (proportional image scaling)Run the server manually:
.venv\Scripts\python.exe -m onenote_mcp.serverTests
Unit tests (no OneNote required):
.venv\Scripts\python.exe -m pytest tests/unit/ -qEnd-to-end tests drive the real MCP server against the real OneNote desktop app. They create pages and leave them in place. Open a notebook named ClaudeSpike in OneNote first: without it, the tests write into the first section of the first open notebook.
.venv\Scripts\python.exe -m pytest -m e2e tests/e2e/ -vReleases and the standalone exe
.github/workflows/release.yml runs the unit tests, builds onenote-mcp.exe,
smoke-tests it and keeps the exe as a workflow artifact for one day.
To release, set version in pyproject.toml and merge it to main. Then either:
In GitHub, open Actions → Release → Run workflow on
mainand tick Publish release. After the tests and the smoke test pass, the run tags that commit (v1.0.1for version1.0.1) and publishes a GitHub release with the exe and its checksum. It refuses to publish from another branch or when the tag already exists.Or push the matching tag, e.g.
git tag v1.0.1andgit push origin v1.0.1. The build fails if the tag and the version differ.
A manual run without the checkbox only builds and tests.
To build locally, use a fresh venv so the build matches CI, with the PyInstaller version pinned in the workflow:
py -m venv %TEMP%\onenote-mcp-build
%TEMP%\onenote-mcp-build\Scripts\python.exe -m pip install . pyinstaller==6.22.3
%TEMP%\onenote-mcp-build\Scripts\python.exe -m PyInstaller packaging/onenote-mcp.spec --noconfirm --clean
%TEMP%\onenote-mcp-build\Scripts\python.exe packaging/smoke_test.py dist/onenote-mcp.exe --require-onenoteThe smoke test speaks MCP over stdio to the exe: it checks that the exe exposes the
same tools as the source and that ping answers, then starts it again with
ONENOTE_DISABLE_RAW_XML=1 and checks for exactly the 9 tools without the raw-XML
ones. --require-onenote additionally requires a responsive OneNote and a working
get_notebooks; CI runs without it because the runner has no OneNote.
License
MIT, see LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Markdown-based note-taking with a hosted MCP server. Your notes serve you and your AI.
Google Keep-style notes app with an MCP server for AI agents to read/write notes.
MCP server for OpenAI API (chat completions, image generation, embeddings) via AceDataCloud
MCP-native open-source Notion alternative: read & write pages, databases and kanban boards.
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceAn MCP server that enables AI assistants to programmatically browse and interact with OneNote notebooks shared via web links through browser automation.16 npm4MIT
- AlicenseNot gradedqualityDmaintenanceA Model Context Protocol server that enables AI language models like Claude to interact with Microsoft OneNote, allowing access to notebooks, creating pages, searching notes, and analyzing content directly through the AI interface.31 npm125MIT
- AlicenseNot gradedqualityDmaintenanceA pure-local Microsoft OneNote MCP server for Windows that controls the OneNote desktop app through the local OneNote COM API without needing Azure, Microsoft Graph, API keys, or OAuth.1MIT
- AlicenseAqualityAmaintenanceMCP server that enables AI assistants to interact with Microsoft OneNote, allowing listing, reading, searching, and creating notes.191MIT