Uptime ratio (90 days)      
v2026.4.101

📚 P3X OneNote MCP — Model Context Protocol server for Microsoft OneNote with read/write, table patching, strikethrough and cost calculation tools

A Node.js MCP server that gives Claude Code (or any MCP client) read + write access to Microsoft OneNote notebooks via the Microsoft Graph API. Beyond the usual list/read/create, it supports table editing: strike rows for done items, patch individual cells, and compute column totals directly on the page.


TL;DR — what you'll do once 

  1. Register a public-client app in Microsoft Entra to get a client ID.
  2. Install this MCP and point Claude Code at it, with your client ID as an env var.
  3. In a new Claude Code conversation, say "authenticate with onenote" — browser sign-in, done.

Then you can list notebooks/pages, read/write HTML, and manipulate tables.


1. Register an Azure public-client app (one-time, ~5 minutes) 

Heads-up: the Entra portal UI is not simple and Microsoft keeps moving things around. There are three separate pages you need to touch (App registration, API permissions, Authentication) and one toggle that's hidden inside a tab. Follow the steps in order — every one is required. Two common gotchas are flagged inline below.

The MCP uses Microsoft's device code flow (no callback URL needed on your side), which requires a public-client app:

  1. Open https://entra.microsoft.com → sign in with the account that owns the OneNote notebooks you want to edit.
  2. Left sidebar → App registrations+ New registration.
  3. Fill in:
    • Name: p3x-onenote-mcp (or anything)
    • Supported account types: pick "Accounts in any organizational directory (Any Entra ID tenant) and personal Microsoft accounts". Do NOT pick "Personal Microsoft account users" even if you only intend to use a personal account — that setting blocks device code flow with the confusing error "The application is a first party application, the user does not have consent…". The broader setting works for personal accounts too.
    • Redirect URI: platform = Public client/native (mobile & desktop), URI = https://login.microsoftonline.com/common/oauth2/nativeclient
  4. Click Register. On the Overview page, copy the Application (client) ID (GUID). You'll need it below.
  5. Left sidebar of the new app → API permissions+ Add a permissionMicrosoft GraphDelegated permissions → tick both Notes.ReadWrite (no .All) and Notes.ReadWrite.All, plus User.ReadAdd permissions. The MCP requests Notes.ReadWrite because .All is not available on personal Microsoft accounts — having both means the app works for personal AND work/school users.
  6. Back on the API permissions page, click Grant admin consent for [your directory] if the button is present. Optional for personal-account-only apps — you'll consent at first sign-in anyway.
  7. Gotcha — the hidden toggle. Left sidebar → Authentication (Preview) (or just "Authentication", depending on the UI you get). At the top of that page there are tabs: Redirect URI configuration | Supported accounts | Settings. Click the Settings tab — not the default first tab. Scroll to Allow public client flows and flip "Enable the following mobile and desktop flows" to YesSave at the top. Without this, device code requests fail with AADSTS70002: The client application must be marked as 'mobile'. Adding the redirect URI in step 3 is necessary but not sufficient; this separate toggle must also be on.

2. Install the MCP and register it with Claude Code 

npm install -g p3x-onenote-mcp
claude mcp add onenote \
  --env P3X_ONENOTE_CLIENT_ID=<client-id> \
  --env P3X_ONENOTE_TENANT=<tenant> \
  -- p3x-onenote-mcp

Option B — from source (dev / unpublished) 

git clone https://git.patrikx3.com/onenote-mcp.git
cd onenote-mcp
yarn install
claude mcp add onenote \
  --env P3X_ONENOTE_CLIENT_ID=<client-id> \
  --env P3X_ONENOTE_TENANT=<tenant> \
  -- /usr/bin/node $PWD/index.mjs

Choosing `<tenant>` 

Your Azure app's "Supported account types"P3X_ONENOTE_TENANT
Any Entra ID tenant + Personal Microsoft accounts (recommended)(omit or common)
Accounts in any organizational directoryorganizations
Single tenantyour directory (tenant) ID
Personal Microsoft account usersavoid — see step 3 above

If omitted, P3X_ONENOTE_TENANT defaults to common, which is right for the recommended account type.

3. Authenticate (first time only) 

Restart Claude Code so it picks up the new MCP. In a new conversation:

"authenticate with onenote"

The tool replies with a URL and a short code:

Visit: https://www.microsoft.com/link        (or https://microsoft.com/devicelogin)
Code:  ABCD-1234
Expires in: 900s

Which URL? Microsoft returns https://www.microsoft.com/link for the consumers tenant and https://microsoft.com/devicelogin for common / organizations. Both accept the same code — if one dumps you on your account home instead of a code-entry page, try the other.

Open the URL, paste the code, sign in with your Microsoft account, approve the permissions prompt. Polling runs in the background — once you finish sign-in, tokens are cached at ~/.config/p3x-onenote-mcp/tokens.json automatically. Call auth_status to confirm, or just call any other tool. Refresh happens silently after that — you only do this dance once.

Use a private / incognito window. If your normal browser has active Microsoft cookies (account.microsoft.com, Outlook, Teams, etc.), login.microsoft.com/device / microsoft.com/devicelogin will silently bounce you to the account dashboard instead of showing the code-entry page, and the flow gets stuck. A fresh private window (Firefox Ctrl+Shift+P, Chrome Ctrl+Shift+N) skips all that — the code-entry page shows up first try. Tested working in Firefox private mode; Chrome incognito behaves the same way.

Enter the most recent code only. Every call to authenticate issues a new device code, and only the latest one is being polled. Earlier codes, even though Microsoft accepts them and shows a success page, leave the MCP unaware because the matching poller no longer exists. If in doubt, re-run authenticate right before you go to the browser.

4. Use it 

Some example prompts after auth:

  • "List my OneNote notebooks"
  • "Show pages in my 'Work' section"
  • "Search onenote pages for 'Munka'"
  • "Open the 'Telekom 2026' page and strike every row where status = Paid"
  • "Read the cost table on page X, sum the Value column, write the total into the last row"

⚠️ Known limitation on personal Microsoft accounts 

Table write tools (patch_cell, strike_row, calc_total with writeToCellId, update_page targeting <td>/<tr>) DO NOT WORK on personal / consumer Microsoft accounts (*@outlook.com, *@hotmail.com, *@live.com, *@windowslive.com, *@msn.com). Microsoft Graph's OneNote PATCH endpoint silently strips <td> and <tr> wrappers from PATCH content on personal accounts, no matter what attributes you include — the cell gets destroyed and its content lands as a bare <p> outside the table. The Graph docs claim this works; the implementation on consumer tenants does not. Tested 2026-04-22.

Work/school accounts may behave differently (not verified in this repo). For personal accounts, treat this MCP as read-only for table content. Editing OneNote tables from a personal account requires OneNote web, desktop (Windows/Mac), or mobile — not Graph.

Roadmap — PDF → reconciled table page (workaround for the PATCH limitation) 

create_page produces perfect, fully-addressable tables on personal accounts. Graph does NOT strip <td>/<tr> on page creation — only on PATCH. That turns the API into an append-only ledger: instead of mutating an existing reference page, each reconciliation cycle emits a new page with a freshly-built table.

Why this shape is the right one:

  • PATCH of table cells is broken on personal accounts (see section above). Any "edit in place" design is a dead end there.
  • POST /onenote/pages with a full <table> in the body works. create_page round-trips row / cell ids cleanly, and read_table can reparse its own output.
  • Append-only is safer regardless of account type. No OneDrive version-history recovery needed, no lost data in the reference page, every run is an immutable artifact you can diff against.

Intended workflow (roadmap — not all pieces exist yet):

  1. Keep one OneNote page as the read-only reference table (categories, expected rows, any hand-maintained metadata).
  2. A planned pdf_to_rows helper extracts line items from a PDF — one row per entry, typed as { date, description, amount }.
  3. A planned import_pdf_total tool matches extracted rows against the reference page (by amount + fuzzy description), then emits a new OneNote page containing:
    • One <tr> per entry — matched rows inherit category / notes from the reference page, unmatched rows are flagged.
    • A computed total row at the bottom, summing the amount column the same way calc_total does (tolerant to 1 234,56, 13 519, 508k, 1.2M, and mixed thousands/decimal separators).
    • A header linking back to the reference page + the source PDF filename + ISO date of the run.
  4. Review the new page. If something's wrong, discard it and re-run — the reference page is never touched.

Status: design locked, pdf_to_rows and import_pdf_total are open TODOs. The number-parsing half is already reusable — see parseNumber in lib/table.mjs.

Tools 

ToolPurpose
authenticateStart device code flow
auth_statusCheck whether tokens are cached
list_notebooksEnumerate notebooks
list_sectionsEnumerate sections in a notebook
list_pagesEnumerate pages in a section
get_pageFetch page HTML (with data-ids for patching)
search_pagesTitle substring search
create_pageCreate a new page (raw HTML)
update_pagePATCH commands — replace/append/insert/prepend by data-id
delete_pageDelete page
read_tableReturn page tables as structured JSON (rows → cells with ids)
patch_cellReplace a single table cell by cell data-id
strike_rowApply strikethrough to every cell in a row
calc_totalSum a numeric column, optionally write the result into a target cell

calc_total parses numbers tolerantly: 1 234,56 Ft, 1,234.56, 1.234,56, 13 519, and suffix-rounded values like 5k, 508k, 1.2M.

Environment variables 

  • P3X_ONENOTE_CLIENT_IDrequired, your Azure app Application (client) ID
  • P3X_ONENOTE_TENANT — optional; common, consumers, organizations, or a specific tenant ID. Defaults to common.
  • P3X_ONENOTE_CONFIG_DIR — optional, defaults to ~/.config/p3x-onenote-mcp. Tokens live under this dir.

Notes on tables 

OneNote tables come back from Graph as HTML with element ids on the id attribute (format td:{guid}{n}, tr:{guid}{n}, table:{guid}{n}) — NOT data-id. ?includeIDs=true just guarantees these ids are present on every element. This MCP adds includeIDs=true automatically for read_table, patch_cell, strike_row, and calc_total. PATCH commands target the full id string verbatim.

For custom work, call get_page (includeIDs defaults to true) and feed the ids to update_page.

Troubleshooting 

  • AADSTS70002: client must be marked as 'mobile': your Azure app has the redirect URI but not the "Allow public client flows" toggle. Go to AuthenticationAdvanced settings → enable it. See step 7 of Azure setup.
  • Other AADSTS... errors: the tenant in P3X_ONENOTE_TENANT doesn't match your app's supported account types. See the tenant table above.
  • Tool says "Not authenticated": either you haven't run authenticate yet, or the refresh token expired (~90 days idle for personal accounts). Just run authenticate again.
  • Graph GET ... → 401 on /me/onenote/... but /me works: the token has User.Read but not a Notes scope Microsoft accepted. Personal accounts need the plain Notes.ReadWrite scope (the .All variant is work/school only). Add Notes.ReadWrite in API permissions and ensure the MCP requests it.
  • Consent screen only shows "Read your profile" (no Notes permission): same underlying problem — Microsoft silently dropped the Notes scope because the version requested doesn't match what the app has. Use Notes.ReadWrite (delegated) rather than .All for personal accounts.
  • Graph GET ... → 403: different problem — the app is missing a permission entirely. Re-check API permissions in Entra.
  • Opaque token starting with Ew (not a JWT): Microsoft is issuing the legacy MSA compact format for personal accounts. This works with Graph if the scope is acceptable — "not a JWT" does not mean "broken".
  • After changing env vars or permissions: restart Claude Code — MCP processes are cached per session.

Corifeus Network 

AI-powered network & email toolkit — free, no signup.

Web · network.corifeus.com MCP · npm i -g p3x-network-mcp

  • AI Network Assistant — ask in plain language, get a full domain health report
  • Network Audit — DNS, SSL, security headers, DNSBL, BGP, IPv6, geolocation in one call
  • Diagnostics — DNS lookup & global propagation, WHOIS, reverse DNS, HTTP check, my-IP
  • Mail Tester — live SPF/DKIM/DMARC + spam score + AI fix suggestions, results emailed (localized)
  • Monitoring — TCP / HTTP / Ping with alerts and public status pages
  • MCP server — 17 tools exposed to Claude Code, Codex, Cursor, any MCP client
  • Installclaude mcp add p3x-network -- npx p3x-network-mcp
  • Try"audit example.com", "why do my emails land in spam? test me@example.com "
  • Sourcepatrikx3/network · patrikx3/network-mcp
  • Contactpatrikx3.com · donate

❤️ Support Our Open-Source Project 

If you appreciate our work, consider ⭐ starring this repository or 💰 making a donation to support server maintenance and ongoing development. Your support means the world to us—thank you!


🌍 About My Domains 

All my domains, including patrikx3.com , corifeus.eu , and corifeus.com, are developed in my spare time. While you may encounter minor errors, the sites are generally stable and fully functional.


📈 Versioning Policy 

Version Structure: We follow a Major.Minor.Patch versioning scheme:

  • Major: 📅 Corresponds to the current year.
  • Minor: 🌓 Set as 4 for releases from January to June, and 10 for July to December.
  • Patch: 🔧 Incremental, updated with each build.

🚨 Important Changes: Any breaking changes are prominently noted in the readme to keep you informed.

P3X-ONENOTE-MCP Build v2026.4.101

NPM Donate for PatrikX3 / P3X Contact Corifeus / P3X Like Corifeus @ Facebook