Skip to main content
Glama
seanjmartin

Evernote MCP Server

by seanjmartin

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

Related MCP server: Notes Manager

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.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
4dRelease cycle
2Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/seanjmartin/evernote-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server