使用说明
Evernote MCP服务器
Python MCP(模型上下文协议)服务器,使AI助手可以完全访问您的Evernote帐户。13个用于搜索、阅读、创建、更新和删除笔记的工具;管理标签和笔记本;处理附件和加密内容;监控写入操作。
需要Python 3.12以上。
快速开始
# Install
pip install -e .
# Authenticate (one-time, opens browser)
python src/evernote_client.py --auth
# Run the server
python src/evernote_mcp.py
认证
服务器使用OAuth 1.0a访问您的Evernote帐户。您需要进行一次身份验证;令牌持续大约一年。
创建令牌
python src/evernote_client.py --auth
这将:
- 打开默认浏览器进入Evernote的授权页面
- 在上启动回调服务器
localhost:8080 - 授权后,Evernote将重定向回回调
- 令牌保存到
.evernote-token.json在项目根中
如果浏览器流失败(例如,在无头服务器上运行),脚本将提示您粘贴 oauth_verifier 5分钟超时后手动设置值。
令牌文件
令牌存储在 .evernote-token.json:
{
"access_token": "S=s46:U=...:E=...:H=...",
"created": 1771265208024,
"expires": 1802801208024
}
access_token-用于所有API调用的OAuth令牌字符串created--获得令牌时的Unix毫秒数expires--令牌过期时的Unix毫秒数(约1年)
令牌过期后,重新运行 python src/evernote_client.py --auth 买一个新的。
验证令牌
python src/evernote_client.py --test
这将测试连接并打印您的用户名、用户ID和笔记本计数。
配置
命令行参数
运输方式 (互斥;默认为stdio):
| 标志 | 默认值 | 描述 |
|---|---|---|
--stdio |
在(未指定其他传输时) | 克劳德代码/MCP客户端集成的stdio传输 |
--http 或 --sse |
关闭 | HTTP/SSE服务器 |
--https |
关闭 | HTTPS/SSE服务器(需要 --ssl-cert 和 --ssl-key) |
HTTP选项:
| 标志 | 默认值 | 描述 |
|---|---|---|
--http-port |
8082 |
HTTP服务器端口 |
--http-host |
127.0.0.1 |
HTTP服务器绑定地址 |
HTTPS选项:
| 标志 | 默认值 | 描述 |
|---|---|---|
--https-port |
8443 |
HTTPS服务器端口 |
--https-host |
127.0.0.1 |
HTTPS服务器绑定地址 |
--ssl-cert |
(必需) | SSL证书文件的路径 |
--ssl-key |
(必需) | SSL私钥文件的路径 |
令牌和队列:
| 标志 | 默认值 | 描述 |
|---|---|---|
--token-file |
.evernote-token.json |
OAuth令牌文件的路径 |
--queue-pace |
10 |
排队写入操作之间的秒数 |
环境变量
| 变量 | 默认值 | 描述 |
|---|---|---|
EVERNOTE_TOKEN_FILE |
.evernote-token.json |
令牌文件的路径(被覆盖 --token-file) |
EVERNOTE_QUEUE_PACE_SECONDS |
10 |
写入队列步调(被覆盖 --queue-pace) |
EVERNOTE_CONSUMER_KEY |
mcpserver-6972 |
OAuth消费者密钥 |
EVERNOTE_CONSUMER_SECRET |
(内置) | OAuth消费者秘密 |
EVERNOTE_SANDBOX |
false |
设置为 true 沙盒(注意:Evernote沙盒已退役) |
Claude代码集成
将此添加到您的 .mcp.json (或克劳德代码设置):
{
"mcpServers": {
"evernote": {
"command": "python",
"args": ["/path/to/evernote-mcp/src/evernote_mcp.py"],
"env": {
"EVERNOTE_TOKEN_FILE": "/path/to/evernote-mcp/.evernote-token.json"
}
}
}
}
使用示例
# 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
工具参考
总结
| 工具 | 类型 | 描述 |
|---|---|---|
list_notebooks |
阅读 | 列出所有笔记本 |
list_tags |
读取 | 列出所有具有层次结构的标签 |
search_notes |
阅读 | 搜索笔记;可选择获取每个笔记本/标签的计数 |
get_note |
阅读 | 以Markdown格式阅读笔记;版本历史;相关注释 |
get_attachment |
读取 | 获取资源元数据、数据和OCR文本 |
get_user_info |
阅读 | 帐户信息、保费状态、上传配额 |
create_note |
写 | 从Markdown创建笔记或克隆现有笔记 |
update_note |
写入 | 编辑内容、元数据、标签、附件 |
delete_note |
写 | 将便条移到垃圾箱(可恢复30天) |
manage_tags |
写入 | 创建、重命名或删除标签 |
add_attachment |
写入 | 将文件附加到现有笔记 |
manage_notebooks |
写/读 | 创建、更新笔记本;获取默认笔记本 |
check_queue |
队列 | 监视和管理排队的写入操作 |
所有写工具都要经过一个有节奏的队列,以避免Evernote的速率限制。如果队列为空,并且已过起搏间隔,则立即执行写入。否则,它们将在后台排队和处理。
所有工具归还 {"success": true, ...} 关于成功或 {"success": false, "error": "...", "error_type": "..."} 失败。
阅读工具
list_notebooks
列出所有Evernote笔记本。
参数: 无
退货: notebooks (列表 {guid, name, stack, defaultNotebook, created, updated}), count
list_tags
列出所有具有父层次结构和完整路径的标签。
参数: 无
退货: tags (列表 {guid, name, parentGuid, parentName, path}), count
这 path 字段显示完整的层次结构,例如。 "Parent/Child/Grandchild".
search_notes
使用Evernote的搜索语法搜索笔记。还可以返回每个笔记本和标签的笔记计数,而不是笔记结果。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
query |
str | "" |
搜索查询(请参见 搜索语法) |
notebook |
str | "" |
此笔记本的范围搜索(名称或GUID) |
max_results |
int | 25 |
可退回的最大钞票数 |
offset |
int | 0 |
分页偏移 |
counts_only bool的。 False |
返回每个笔记本/标签的笔记计数,而不是笔记列表 |
回报(正常): notes (列表 {guid, title, created, updated, notebookGuid, notebookName, tags, ...}), totalNotes, offset, hasMore
退货(仅限counts_only): notebookCounts (数到名字), tagCounts, trashCount
get_note
以Markdown格式阅读笔记内容。还支持版本历史记录(高级)和查找相关注释。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
guid |
str | 必需 | 注意GUID |
include_content bool的。 True |
包括注释正文 | ||
passphrase |
str | "" |
解密 <en-crypt> 与此密码短语内联的块 |
list_versions bool的。 False |
返回版本历史记录而不是内容(高级) | ||
version |
int | 0 |
通过updateSequenceNum返回特定版本(高级) |
find_related bool的。 False |
包括相关注释和标签 |
回报(正常): note 随着 {guid, title, content_markdown, notebookGuid, notebookName, tags, created, updated, attributes, resources}
返回(list_versions): versions (列表 {updateSequenceNum, updated, saved, title})
返回(版本>0): 该特定版本的注释内容
返回值(find_related): 正常的音符响应,带有额外的 related 包含相关注释和标签的字段
get_attachment
获取资源/附件元数据,以及可选的二进制数据或OCR识别文本。
通过任一方式识别资源 resource_guid 或以下各项的组合 note_guid + resource_hash.
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
resource_guid |
str | "" |
直接资源GUID |
note_guid |
str | "" |
注释GUID(与 resource_hash) |
resource_hash |
str | "" |
资源的MD5十六进制哈希 |
include_data bool的。 False |
包含base64编码的文件数据 | ||
include_recognition bool的。 False |
包括OCR/识别文本 |
退货: resource 随着 {guid, noteGuid, mime, hash, size, filename, width, height} 加 data_base64 (如有要求)以及 recognition (如有要求)
get_user_info
获取帐户信息、保费状态、上传配额和同步状态。
参数: 无
退货: username, email, name, userId, privilege, accounting (上传限制), premium (状态、到期), syncState (当前时间、更新计数、上传字节数)
写入工具
所有写工具都是通过有节奏的队列提交的。当立即执行时,它们直接返回操作结果。排队后,它们会返回:
{
"success": true,
"queued": true,
"job_id": "abc12345",
"message": "Queued. Use check_queue to monitor.",
"queue_position": 1
}
create_note
从Markdown内容创建新笔记,或克隆现有笔记。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
title |
str | 必填 | 注释标题 |
content |
str | "" |
Markdown中的注释正文 |
notebook |
str | "" |
目标笔记本(名称或GUID;如果为空,则使用默认值) |
tags |
str | "" |
逗号分隔的标记名(如果不存在,则自动创建) |
attachment_paths |
str | "" |
要附加的逗号分隔的本地文件路径 |
copy_from_guid |
str | "" |
克隆此笔记,而不是从内容创建 |
当 copy_from_guid 已设置 title, content,以及 tags 参数被忽略。这 notebook 参数可以覆盖目标笔记本。
退货: note 随着 {guid, title, notebookGuid, notebookName, created}
update_note
更新现有笔记的内容、元数据或两者。当仅元数据更改时,自动使用仅限元数据的轻量级路径(1个API调用);当内容或附件更改时,则自动使用完整内容路径(2个API调用)。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
guid |
str | 必需 | 注意GUID |
title |
str | "" |
新标题(空=保持最新) |
content |
str | "" |
新Markdown内容(空=保持最新) |
tags |
str | "" |
逗号分隔的标记名称,替换所有(空=保持最新) |
notebook |
str | "" |
移动到此笔记本(空=保持最新) |
created |
str | "" |
覆盖创建日期(ISO或YYYYMMDD) |
reminder_time |
str | "" |
设置提醒(ISO或YYYYMMDD) |
author |
str | "" |
设置作者属性 |
source_url |
str | "" |
设置源URL属性 |
latitude |
float | 无 | 设置纬度 |
longitude |
float | 无 | 设置经度 |
attachment_paths |
str | "" |
要附加的逗号分隔的本地文件路径 |
退货: note 随着字段的更新, update_type ("metadata_only" 或 "full_content")
delete_note
把一张纸条移到垃圾桶里。可通过Evernote UI恢复30天。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
guid |
str | 必需 | 注意GUID |
退货: guid, deleted: true
manage_tags
创建、重命名或删除标记。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
action |
str | 必填 | "create", "rename",或 "delete" |
name |
str | "" |
标记名称(用于创建,或查找以重命名/删除) |
guid |
str | "" |
标记GUID(用于重命名/删除的名称替代) |
new_name |
str | "" |
新名称(用于重命名) |
parent |
str | "" |
父标记名称或GUID(用于创建) |
add_attachment
将文件附加到现有笔记。提供本地文件路径或具有文件名和MIME类型的base64数据。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
note_guid |
str | 必需 | 注意要附加的GUID |
file_path |
str | "" |
本地文件的路径 |
data_base64 |
str | "" |
Base64编码文件数据(file_path的替代方案) |
filename |
str | "" |
文件名(data_base64需要) |
mime_type |
str | "" |
MIME类型(data_base64需要MIME类型,例如。 "image/png") |
manage_notebooks
创建或更新笔记本,或获取默认笔记本。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
action |
str | 必填 | "create", "update",或 "get_default" |
name |
str | "" |
笔记本名称 |
guid |
str | "" |
笔记本GUID(用于更新) |
new_name |
str | "" |
新名称(用于更新) |
stack |
str | "" |
堆栈名称(用于创建/更新) |
这 get_default 动作是一个读取操作,并立即执行(绕过写入队列)。
排队工具
check_queue
监视和管理排队的写入操作。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
job_id |
str | "" |
检查特定作业(空=显示全部) |
status |
str | "" |
筛选器: "pending", "processing", "completed", "failed" |
clear_completed bool的。 False |
从历史记录中删除已完成/失败的作业 |
退货: jobs 列表, count, summary (按状态计数), paused (布尔), pause_remaining_seconds, pace_seconds
建筑
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
源文件
src/evernote_mcp.py --服务器入口点。定义所有13个 @mcp.tool() 函数、用于多传输启动的argparse以及队列调用的写执行器函数。读取工具直接调用API;编写工具提交到队列。
src/evernote_client.py — EvernoteAPI 类包装Evernote SDK。所有方法都返回纯字典。处理OAuth流、令牌持久性、速率限制检测、RTE(实时编辑)冲突检测、名称/GUID解析缓存以及具有指数回退的瞬态错误重试。
src/enml_converter.py --双向ENML到Markdown转换器。笔记在Evernote中存储为ENML,但MCP服务器接受并返回Markdown。资源被引用为 。还处理AES-128-CBC解密 <en-crypt> 阻碍。
src/write_queue.py --所有写入操作的持久定步调队列。默认速度:API调用间隔10秒。如果Evernote返回速率限制,队列将暂停服务器指定的持续时间。队列状态将持久化为 .evernote-queue.json 因此,挂起的作业在服务器重启后仍然有效。
关键模式
写入队列步调 --写入操作的节奏是为了避免Evernote的速率限制。默认写入间隔为10秒(--queue-pace 或 EVERNOTE_QUEUE_PACE_SECONDS).如果队列为空,并且自上次写入以来已经过了足够的时间,则操作将立即执行。否则,它们将被排队并由后台线程处理。
费率限制与RTE冲突 -Evernote的API回报 RATE_LIMIT_REACHED 针对两种不同的情况。真实速率限制使用长持续时间(900-3600秒)。当笔记在Evernote桌面客户端中打开(实时编辑)时,API也会返回 RATE_LIMIT_REACHED 但持续时间短(<300秒)。服务器使用300秒的阈值来区分这些:短持续时间增加 RTEConflictError,长时间上涨 RateLimitError.
元数据与内容更新 — update_note 自动选择最有效的路径。如果只有元数据在更改(标题、标记、笔记本、日期),则使用单个API调用来避免RTE冲突。如果内容或附件正在更改,它将使用双调用路径(get+update)。
ENML转换 --笔记在Evernote中存储为ENML(一种受限的XML格式)。服务器自动转换Markdown。资源(图像、文件)使用以下方式引用 evernote-resource:<md5hash> URL。转换器处理复选框(<en-todo>),加密块(<en-crypt>),并删除不允许使用的HTML标签。
搜索语法
这 search_notes 该工具接受Evernote的搜索语法:
| 操作员 | 示例 | 描述 |
|---|---|---|
notebook: |
notebook:"My Notebook" |
在特定笔记本中搜索 |
tag: |
tag:important |
带有此标签的注释 |
-tag: |
-tag:archive |
没有此标签的注释 |
intitle: |
intitle:meeting |
仅在笔记标题中搜索 |
created: |
created:20240101 |
在此日期或之后创建的注释(YYYYMMDD) |
updated: |
updated:20240601 |
在此日期或之后更新的注释 |
resource: |
resource:image/png |
包含此MIME类型的注释 |
todo: |
todo:true |
带有未选中复选框的注释 |
* |
meet* |
通配符(匹配“会议”、“会议”等) |
"..." |
"exact phrase" |
精确短语匹配 |
any: |
any: cat dog |
匹配任何术语(默认为全部) |
- |
-archive |
排除术语 |
操作员可以组合: notebook:"Work" tag:urgent intitle:project created:20240101
错误处理
所有工具都返回一致的响应格式:
{"success": true, "...": "..."}
出现错误时:
{"success": false, "error": "description", "error_type": "ERROR_CODE"}
| 错误类型 | 原因 | 解决方法 |
|---|---|---|
RATE_LIMITED |
Evernote API配额耗尽 | 等待 retry_after_seconds (已包含在回复中) |
RTE_CONFLICT |
笔记已在Evernote桌面客户端中打开 | 关闭Evernote中的笔记并重试 |
NOT_FOUND |
未找到笔记本或资源 | 请检查名称/GUID |
ERROR |
其他错误 | 查看错误消息以了解详细信息 |
排队写入失败标记为 "failed" 在队列中。使用 check_queue 查看失败的作业及其错误消息。
发展
设置
pip install -e ".[dev]"
运行测试
# 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_enml_converter.py |
40 | ENML/Markdown转换、解密 |
test_evernote_client.py |
69 | API包装器、OAuth、速率限制、所有SDK方法 |
test_evernote_mcp.py |
92 | MCP工具功能、执行器、错误处理 |
test_write_queue.py |
29 | 队列逻辑、持久性、节奏、后台处理 |
test_integration.py |
3 | 真正的API调用(默认情况下跳过) |
230个单元测试,95%以上的代码覆盖率。测试模拟Evernote SDK note_store/user_store 客户端测试级别和模拟 EvernoteAPI MCP工具测试方法。
依赖项
运行时间: evernote3, oauth2, mcp, fastmcp, markdownify, markdown-it-py, beautifulsoup4, lxml, cryptography, uvicorn
发展: pytest, pytest-mock, pytest-cov
许可证
版权所有(c)2026肖恩·马丁。MIT许可证。看 许可证 了解详情。