项目源码地址:obsidian-mcp-python
介绍
现在mcp相关信息满天飞,但大多都在搞理论。不如动手搞个mcp服务,让它跑起来才能真正感受到mcp是个啥。另外,目前的免费教程大多数是用 js/ts 写的,python版本还真是少之又少。刚好自己第一个mcp服务是python写的,虽然小但也方便理解。重点即便今天搜了mcp python相关资料,依旧很少,那就水一篇吧。后续会系统写下如何基于fastMCP开发MCP服务。
开发
MCP 前置知识
关于什么是mcp,太多人讲了,可以自行查下。我这里也推荐几篇,其中的实战是基于js的,不过不妨碍整理理解mcp的核心概念和能力
Python 开发工具
如果是第一次使用python的话,工具篇可以直接选uv,类似JS生态中的npm,关于 UV,可以看下我写的这个:Python管理工具UV。
python中有 虚拟环境 的概念对于前端同学可能比较陌生,简单讲,虚拟环境 相当于nvm + npm 管理下的前端项目,一个python项目跑在一个指定的虚拟环境下,有指定的python版本和依赖库,这样可以使得不同项目有自己专门的python版本和依赖库版本。
MCP 服务开发
先简单介绍下该mcp的功能:可以操作obsidian中的markdown文件,如读取文件数量、返回文件内容、新建文件。
下面是具体开发过程:
pip install uv
安装uv工具uv init obsidian-mcp-python
创建项目目录cd obsidian-mcp-python
uv venv
新建虚拟环境source .venv/bin/activate
开启虚拟环境uv add "mcp[cli]"
安装指定依赖,mcp这个包提供了fastMCP FastMCP是官方推荐的一个基于python的mcp开发框架,使用类似nestjs中装饰器的风格提供tools、resources、prompt等能力,不过我们这个例子中只设计tools的使用。- 新建一个main.py文件,所有的功能都在里面了。
python
import os
import glob
from mcp.server.fastmcp import FastMCP
mcp = FastMCP()
# 从环境变量中获取 Obsidian 根目录路径,默认为当前目录
OBSIDIAN_ROOT = os.environ.get("OBSIDIAN_PATH", ".")
@mcp.tool()
def count_markdown_files():
"""获取 Obsidian 库中所有 Markdown 文件的数量"""
md_files = glob.glob(os.path.join(OBSIDIAN_ROOT, "**/*.md"), recursive=True)
return len(md_files)
@mcp.tool()
def get_all_markdown_contents():
"""获取 Obsidian 库中所有 Markdown 文件的内容"""
md_files = glob.glob(os.path.join(OBSIDIAN_ROOT, "**/*.md"), recursive=True)
result = []
for file_path in md_files:
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 获取相对路径作为文件标识符
relative_path = os.path.relpath(file_path, OBSIDIAN_ROOT)
result.append({
"file": relative_path,
"content": content
})
except Exception as e:
result.append({
"file": os.path.relpath(file_path, OBSIDIAN_ROOT),
"error": str(e)
})
return result
@mcp.tool()
def create_markdown_file(filename, content=""):
"""在 Obsidian 库中创建一个新的 Markdown 文件
Args:
filename: Markdown 文件名 (不需要 .md 后缀,会自动添加)
content: 文件的初始内容 (可选)
"""
# 确保有 .md 后缀
if not filename.endswith('.md'):
filename += '.md'
# 创建完整路径
file_path = os.path.join(OBSIDIAN_ROOT, filename)
# 检查文件是否已存在
if os.path.exists(file_path):
return {"success": False, "error": f"文件 {filename} 已存在"}
try:
# 确保目录存在
os.makedirs(os.path.dirname(file_path), exist_ok=True)
# 创建文件
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
return {"success": True, "path": os.path.relpath(file_path, OBSIDIAN_ROOT)}
except Exception as e:
return {"success": False, "error": str(e)}
if __name__ == "__main__":
print(f"Obsidian MCP 服务已启动,使用路径: {OBSIDIAN_ROOT}")
mcp.run(transport="stdio")
现在我们借助 @mcp.tool()
写了3个工具函数,这几个工具是让llm去调的,比如在cursor的ai会话中,当你让他操作obsidian时,他会自动找到可用的mcp,判断应该调哪个工具。
接下来就是写mcp 的配置json,我就把我自己配的原样输出了(更便于理解),地址部分改成自己本地地址就行
json
{
"mcpServers": {
"obsidian-mcp-python": {
"command": "uv",
"args": [
"--directory",
"/Users/ran/Code/Github/zhangran/obsidian-mcp-python",
"run",
"main.py"
],
"env": {
"OBSIDIAN_PATH": "/Users/ran/Documents/ObsidianNotes"
}
}
}
}
使用
上面的配置就是适合cursor的,直接copy下贴到cursor的mcp配置里就行。
简单贴下效果:

可以看出能调用查询和新建工具了:count_markdown_files
create_markdown_file
然后再打开我的obsidian,发现文章已经写进来了,如下图:

结束~
参考的几个mcp相关资料 FastMCP框架 FastMCP 官网Welcome to FastMCP 2.0 FastMCP脚手架 fastmcp-boilerplate: 简单的FastMCP脚手架基于Typescript FastMCP Typescript版 mcp-obsidian: 一个基于restful的obsidian mcp server