Evernote MCP Server

Evernote MCP Server

A Python MCP server that gives AI assistants full access to your Evernote account. It provides 13 tools for searching, reading, creating, updating, and deleting notes; managing tags and notebooks; handling attachments and encrypted content.

Category
Visit Server

README

Evernote MCP Server

A Python MCP (Model Context Protocol) server that gives AI assistants full access to your Evernote account. 13 tools for searching, reading, creating, updating, and deleting notes; managing tags and notebooks; handling attachments and encrypted content; monitoring write operations.

Requires Python 3.12+.


Quick Start

# Install
pip install -e .

# Authenticate (one-time, opens browser)
python src/evernote_client.py --auth

# Run the server
python src/evernote_mcp.py

Authentication

The server uses OAuth 1.0a to access your Evernote account. You need to authenticate once; the token lasts approximately one year.

Creating the Token

python src/evernote_client.py --auth

This will:

  1. Open your default browser to Evernote's authorization page
  2. Start a callback server on localhost:8080
  3. After you authorize, Evernote redirects back to the callback
  4. The token is saved to .evernote-token.json in the project root

If the browser flow fails (e.g., running on a headless server), the script will prompt you to paste the oauth_verifier value manually after a 5-minute timeout.

Token File

The token is stored in .evernote-token.json:

{
  "access_token": "S=s46:U=...:E=...:H=...",
  "created": 1771265208024,
  "expires": 1802801208024
}
  • access_token — the OAuth token string used for all API calls
  • created — Unix milliseconds when the token was obtained
  • expires — Unix milliseconds when the token expires (~1 year)

When the token expires, re-run python src/evernote_client.py --auth to get a new one.

Verifying the Token

python src/evernote_client.py --test

This tests the connection and prints your username, user ID, and notebook count.


Configuration

Command-Line Arguments

Transport mode (mutually exclusive; stdio is the default):

Flag Default Description
--stdio on (when no other transport specified) stdio transport for Claude Code / MCP client integration
--http or --sse off HTTP/SSE server
--https off HTTPS/SSE server (requires --ssl-cert and --ssl-key)

HTTP options:

Flag Default Description
--http-port 8082 HTTP server port
--http-host 127.0.0.1 HTTP server bind address

HTTPS options:

Flag Default Description
--https-port 8443 HTTPS server port
--https-host 127.0.0.1 HTTPS server bind address
--ssl-cert (required) Path to SSL certificate file
--ssl-key (required) Path to SSL private key file

Token and queue:

Flag Default Description
--token-file .evernote-token.json Path to the OAuth token file
--queue-pace 10 Seconds between queued write operations

Environment Variables

Variable Default Description
EVERNOTE_TOKEN_FILE .evernote-token.json Path to the token file (overridden by --token-file)
EVERNOTE_QUEUE_PACE_SECONDS 10 Write queue pacing (overridden by --queue-pace)
EVERNOTE_CONSUMER_KEY mcpserver-6972 OAuth consumer key
EVERNOTE_CONSUMER_SECRET (built-in) OAuth consumer secret
EVERNOTE_SANDBOX false Set to true for sandbox (note: Evernote sandbox is decommissioned)

Claude Code Integration

Add this to your .mcp.json (or Claude Code settings):

{
  "mcpServers": {
    "evernote": {
      "command": "python",
      "args": ["/path/to/evernote-mcp/src/evernote_mcp.py"],
      "env": {
        "EVERNOTE_TOKEN_FILE": "/path/to/evernote-mcp/.evernote-token.json"
      }
    }
  }
}

Usage Examples

# stdio mode (default, for Claude Code)
python src/evernote_mcp.py

# HTTP on a custom port
python src/evernote_mcp.py --http --http-port 9000

# HTTPS with SSL certificates
python src/evernote_mcp.py --https --ssl-cert cert.pem --ssl-key key.pem

# Custom token file and faster queue pacing
python src/evernote_mcp.py --token-file /secure/token.json --queue-pace 5

Tools Reference

Summary

Tool Type Description
list_notebooks Read List all notebooks
list_tags Read List all tags with hierarchy
search_notes Read Search notes; optionally get counts per notebook/tag
get_note Read Read a note as Markdown; version history; related notes
get_attachment Read Get resource metadata, data, and OCR text
get_user_info Read Account info, premium status, upload quota
create_note Write Create a note from Markdown or clone an existing note
update_note Write Edit content, metadata, tags, attachments
delete_note Write Move a note to trash (recoverable 30 days)
manage_tags Write Create, rename, or delete tags
add_attachment Write Attach a file to an existing note
manage_notebooks Write/Read Create, update notebooks; get default notebook
check_queue Queue Monitor and manage queued write operations

All write tools go through a paced queue to avoid Evernote rate limits. If the queue is empty and the pacing interval has elapsed, writes execute immediately. Otherwise they are queued and processed in the background.

All tools return {"success": true, ...} on success or {"success": false, "error": "...", "error_type": "..."} on failure.


Read Tools

list_notebooks

List all Evernote notebooks.

Parameters: None

Returns: notebooks (list of {guid, name, stack, defaultNotebook, created, updated}), count


list_tags

List all tags with parent hierarchy and full paths.

Parameters: None

Returns: tags (list of {guid, name, parentGuid, parentName, path}), count

The path field shows the full hierarchy, e.g. "Parent/Child/Grandchild".


search_notes

Search notes using Evernote's search grammar. Can also return note counts per notebook and tag instead of note results.

Parameter Type Default Description
query str "" Search query (see Search Grammar)
notebook str "" Scope search to this notebook (name or GUID)
max_results int 25 Maximum notes to return
offset int 0 Pagination offset
counts_only bool False Return note counts per notebook/tag instead of note list

Returns (normal): notes (list of {guid, title, created, updated, notebookGuid, notebookName, tags, ...}), totalNotes, offset, hasMore

Returns (counts_only): notebookCounts (dict of name to count), tagCounts, trashCount


get_note

Read a note's content as Markdown. Also supports version history (premium) and finding related notes.

Parameter Type Default Description
guid str required Note GUID
include_content bool True Include note body
passphrase str "" Decrypt <en-crypt> blocks inline with this passphrase
list_versions bool False Return version history instead of content (premium)
version int 0 Return a specific version by updateSequenceNum (premium)
find_related bool False Include related notes and tags

Returns (normal): note with {guid, title, content_markdown, notebookGuid, notebookName, tags, created, updated, attributes, resources}

Returns (list_versions): versions (list of {updateSequenceNum, updated, saved, title})

Returns (version > 0): The note content at that specific version

Returns (find_related): Normal note response with an additional related field containing related notes and tags


get_attachment

Get resource/attachment metadata and optionally its binary data or OCR recognition text.

Identify the resource by EITHER resource_guid OR the combination of note_guid + resource_hash.

Parameter Type Default Description
resource_guid str "" Direct resource GUID
note_guid str "" Note GUID (use with resource_hash)
resource_hash str "" MD5 hex hash of the resource
include_data bool False Include base64-encoded file data
include_recognition bool False Include OCR/recognition text

Returns: resource with {guid, noteGuid, mime, hash, size, filename, width, height} plus data_base64 (if requested) and recognition (if requested)


get_user_info

Get account information, premium status, upload quota, and sync state.

Parameters: None

Returns: username, email, name, userId, privilege, accounting (upload limits), premium (status, expiration), syncState (current time, update count, uploaded bytes)


Write Tools

All write tools are submitted through a paced queue. When executed immediately, they return the operation result directly. When queued, they return:

{
  "success": true,
  "queued": true,
  "job_id": "abc12345",
  "message": "Queued. Use check_queue to monitor.",
  "queue_position": 1
}

create_note

Create a new note from Markdown content, or clone an existing note.

Parameter Type Default Description
title str required Note title
content str "" Note body in Markdown
notebook str "" Target notebook (name or GUID; uses default if empty)
tags str "" Comma-separated tag names (auto-created if they don't exist)
attachment_paths str "" Comma-separated local file paths to attach
copy_from_guid str "" Clone this note instead of creating from content

When copy_from_guid is set, the title, content, and tags parameters are ignored. The notebook parameter can override the destination notebook.

Returns: note with {guid, title, notebookGuid, notebookName, created}


update_note

Update an existing note's content, metadata, or both. Automatically uses a lightweight metadata-only path (1 API call) when only metadata changes, or a full content path (2 API calls) when content or attachments change.

Parameter Type Default Description
guid str required Note GUID
title str "" New title (empty = keep current)
content str "" New Markdown content (empty = keep current)
tags str "" Comma-separated tag names, replaces all (empty = keep current)
notebook str "" Move to this notebook (empty = keep current)
created str "" Override created date (ISO or YYYYMMDD)
reminder_time str "" Set reminder (ISO or YYYYMMDD)
author str "" Set author attribute
source_url str "" Set source URL attribute
latitude float None Set latitude
longitude float None Set longitude
attachment_paths str "" Comma-separated local file paths to attach

Returns: note with updated fields, update_type ("metadata_only" or "full_content")


delete_note

Move a note to the trash. Recoverable for 30 days through the Evernote UI.

Parameter Type Default Description
guid str required Note GUID

Returns: guid, deleted: true


manage_tags

Create, rename, or delete tags.

Parameter Type Default Description
action str required "create", "rename", or "delete"
name str "" Tag name (for create, or lookup for rename/delete)
guid str "" Tag GUID (alternative to name for rename/delete)
new_name str "" New name (for rename)
parent str "" Parent tag name or GUID (for create)

add_attachment

Attach a file to an existing note. Provide either a local file path OR base64 data with filename and MIME type.

Parameter Type Default Description
note_guid str required Note GUID to attach to
file_path str "" Path to a local file
data_base64 str "" Base64-encoded file data (alternative to file_path)
filename str "" Filename (required with data_base64)
mime_type str "" MIME type (required with data_base64, e.g. "image/png")

manage_notebooks

Create or update notebooks, or get the default notebook.

Parameter Type Default Description
action str required "create", "update", or "get_default"
name str "" Notebook name
guid str "" Notebook GUID (for update)
new_name str "" New name (for update)
stack str "" Stack name (for create/update)

The get_default action is a read operation and executes immediately (bypasses the write queue).


Queue Tool

check_queue

Monitor and manage queued write operations.

Parameter Type Default Description
job_id str "" Check a specific job (empty = show all)
status str "" Filter: "pending", "processing", "completed", "failed"
clear_completed bool False Remove completed/failed jobs from history

Returns: jobs list, count, summary (counts per status), paused (bool), pause_remaining_seconds, pace_seconds


Architecture

MCP Client (Claude Code, etc.)
        |
        v  stdio / HTTP+SSE / HTTPS+SSE
  evernote_mcp.py       -- FastMCP server, 13 tool definitions, transport setup
        |
        +---> write_queue.py       -- Paced write queue, background thread, JSON persistence
        |
        +---> evernote_client.py   -- EvernoteAPI class, OAuth, SDK wrapper, all API methods
        |
        +---> enml_converter.py    -- ENML <-> Markdown conversion, en-crypt decryption

Source Files

src/evernote_mcp.py — Server entry point. Defines all 13 @mcp.tool() functions, argparse for multi-transport startup, and write executor functions that the queue calls. Read tools call the API directly; write tools submit to the queue.

src/evernote_client.pyEvernoteAPI class wrapping the Evernote SDK. All methods return plain dicts. Handles OAuth flow, token persistence, rate limit detection, RTE (Real-Time Editing) conflict detection, name/GUID resolution caches, and transient error retry with exponential backoff.

src/enml_converter.py — Bidirectional ENML-to-Markdown converter. Notes are stored as ENML in Evernote but the MCP server accepts and returns Markdown. Resources are referenced as ![filename](evernote-resource:<hash>). Also handles AES-128-CBC decryption of <en-crypt> blocks.

src/write_queue.py — Persistent paced queue for all write operations. Default pace: 10 seconds between API calls. If Evernote returns a rate limit, the queue pauses for the server-specified duration. Queue state is persisted to .evernote-queue.json so pending jobs survive server restarts.

Key Patterns

Write queue pacing — Write operations are paced to avoid Evernote's rate limits. The default is 10 seconds between writes (--queue-pace or EVERNOTE_QUEUE_PACE_SECONDS). If the queue is empty and enough time has passed since the last write, operations execute immediately. Otherwise they are queued and processed by a background thread.

Rate limit vs. RTE conflict — Evernote's API returns RATE_LIMIT_REACHED for two different situations. True rate limits use long durations (900-3600 seconds). When a note is open in the Evernote desktop client (Real-Time Editing), the API also returns RATE_LIMIT_REACHED but with short durations (<300 seconds). The server distinguishes these using a 300-second threshold: short durations raise RTEConflictError, long durations raise RateLimitError.

Metadata vs. content updatesupdate_note automatically selects the most efficient path. If only metadata is changing (title, tags, notebook, dates), it uses a single API call that avoids RTE conflicts. If content or attachments are changing, it uses a two-call path (get + update).

ENML conversion — Notes are stored as ENML (a restricted XML format) in Evernote. The server converts to/from Markdown automatically. Resources (images, files) are referenced using evernote-resource:<md5hash> URLs. The converter handles checkboxes (<en-todo>), encrypted blocks (<en-crypt>), and strips disallowed HTML tags.


Search Grammar

The search_notes tool accepts Evernote's search grammar:

Operator Example Description
notebook: notebook:"My Notebook" Search within a specific notebook
tag: tag:important Notes with this tag
-tag: -tag:archive Notes without this tag
intitle: intitle:meeting Search in note titles only
created: created:20240101 Notes created on or after this date (YYYYMMDD)
updated: updated:20240601 Notes updated on or after this date
resource: resource:image/png Notes containing this MIME type
todo: todo:true Notes with unchecked checkboxes
* meet* Wildcard (matches "meeting", "meetings", etc.)
"..." "exact phrase" Exact phrase match
any: any: cat dog Match any term (default is all)
- -archive Exclude term

Operators can be combined: notebook:"Work" tag:urgent intitle:project created:20240101


Error Handling

All tools return a consistent response format:

{"success": true, "...": "..."}

On error:

{"success": false, "error": "description", "error_type": "ERROR_CODE"}
Error Type Cause Resolution
RATE_LIMITED Evernote API quota exhausted Wait for retry_after_seconds (included in response)
RTE_CONFLICT Note is open in the Evernote desktop client Close the note in Evernote and retry
NOT_FOUND Notebook or resource not found Check the name/GUID
ERROR Other errors Check the error message for details

Queued writes that fail are marked as "failed" in the queue. Use check_queue to see failed jobs and their error messages.


Development

Setup

pip install -e ".[dev]"

Running Tests

# All tests
python -m pytest tests/ -v

# With coverage report
python -m pytest tests/ --cov=src --cov-report=term-missing

# Integration tests (requires valid token)
EVERNOTE_INTEGRATION=1 python -m pytest tests/test_integration.py -v

Test Structure

File Tests Covers
test_enml_converter.py 40 ENML/Markdown conversion, decryption
test_evernote_client.py 69 API wrapper, OAuth, rate limits, all SDK methods
test_evernote_mcp.py 92 MCP tool functions, executors, error handling
test_write_queue.py 29 Queue logic, persistence, pacing, background processing
test_integration.py 3 Real API calls (skipped by default)

230 unit tests, 95%+ code coverage. Tests mock the Evernote SDK at the note_store/user_store level for client tests, and mock EvernoteAPI methods for MCP tool tests.


Dependencies

Runtime: evernote3, oauth2, mcp, fastmcp, markdownify, markdown-it-py, beautifulsoup4, lxml, cryptography, uvicorn

Development: pytest, pytest-mock, pytest-cov


License

Copyright (c) 2026 Sean Martin. MIT License. See LICENSE for details.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured