TL;DR: I built an open-source MCP server for Microsoft OneNote in Python. It connects Claude (or any MCP-compatible AI) directly to your OneNote notebooks. The tricky part: Microsoft's
copyToSectionAPI returns a 501 on personal accounts. Here's the workaround — and the full story of why I built it.
The Problem: A Notebook Full of Chaos
Three years of notes. 200+ pages. Dozens of sections with names that made sense at the time. Zero structure.
I wanted to reorganize my OneNote notebook, but doing it manually was out of the question. So I asked: what if I could give Claude access to OneNote directly, and let it do the work?
That question led me to build onenote-mcp-server.
What Is MCP?
Model Context Protocol (MCP) is an open standard from Anthropic that lets AI assistants connect to external tools and services through a structured, typed interface. You build an MCP server that exposes "tools" — essentially callable functions — and any MCP client (Claude Desktop, Cursor, Zed, Continue.dev) can call them.
It's the difference between telling your AI "here's a document, help me think about it" and telling your AI "here are your tools, go organize my notebook."
The Architecture
The server is built with Python + FastMCP and talks to OneNote through the Microsoft Graph API. Authentication is handled via MSAL with a local token cache — browser-based OAuth on first run, silent token refresh after that. Everything runs locally on your machine.
The server exposes 8 tools:
onenote_list_notebooks → list all notebooks
onenote_list_section_groups → list section groups
onenote_list_sections → list sections
onenote_list_pages → list pages in a section
onenote_get_page_content → read full HTML of a page
onenote_create_section → create a new section
onenote_copy_page → copy a page to another section
onenote_clone_page → clone with full HTML fidelity
The 501 Wall
Microsoft's Graph API has a copyToSection endpoint documented for moving OneNote pages between sections. It works great on organizational accounts (Office 365).
On personal accounts (Outlook.com, Hotmail, Live.com)? It returns:
HTTP 501 Not Implemented
Not a permissions error. Not a rate limit. The feature is simply not implemented for personal accounts. The docs don't make this obvious — you find out the hard way.
The Workaround: Read HTML → POST to New Section
The fix is conceptually simple:
- Fetch the source page's HTML:
GET /me/onenote/pages/{id}/content - POST that HTML to the target section:
POST /me/onenote/sections/{id}/pages
@mcp.tool()
async def onenote_clone_page(page_id: str, target_section_id: str) -> str:
"""Clone a OneNote page to a target section (works on personal accounts)."""
# Step 1: Read the source page HTML
html_content = await graph_get(f"/me/onenote/pages/{page_id}/content")
# Step 2: Recreate it in the target section
response = await graph_post(
f"/me/onenote/sections/{target_section_id}/pages",
data=html_content,
content_type="text/html"
)
return f"Page cloned. New page ID: {response['id']}"
The result is functionally identical to a copy — same title, same content, same formatting. This became the cornerstone of the whole migration: Claude could now move pages in bulk, reading each one to understand its content and routing it to the correct destination section.
Here's the full flow as a sequence diagram:
The Result
With the server running and connected to Claude Desktop, I reorganized 200+ pages in a single session. I described my target structure in plain English, Claude read the pages to understand their content, and moved each one to the right destination.
From chaos to structure in a few hours — work that would have taken a full day manually.
Setup (5 minutes)
Prerequisites: Python 3.8+, a Microsoft account, an Azure App Registration (free)
git clone https://github.com/jisujit/onenote-mcp-server
cd onenote-mcp-server
pip install -r requirements.txt
cp .env.example .env
# Fill in CLIENT_ID and TENANT_ID=common
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"onenote": {
"command": "python",
"args": ["/path/to/onenote-mcp-server/src/server.py"],
"env": {
"CLIENT_ID": "your-client-id",
"TENANT_ID": "common"
}
}
}
}
Use
TENANT_ID=commonfor personal Microsoft accounts. Full Azure setup guide in the README.
Key Takeaways
Test with the actual account type your users will have. Organizational accounts and personal accounts behave differently against the Graph API in ways the docs don't always surface.
FastMCP is excellent. It handles all the MCP protocol boilerplate. You write a decorated Python function, it becomes a callable tool. That's the whole API surface you need to learn.
AI as orchestrator + MCP as the hands = a genuinely new capability. The server provides structure; Claude provides judgment about where content belongs. Neither is sufficient alone.
Repo
GitHub: github.com/jisujit/onenote-mcp-server
Issues, PRs, and stars welcome. If you're building MCP servers for other Microsoft 365 tools, I'd love to hear what you're running into.


Top comments (0)